<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/rss/styles.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>luckycc</title><description>Check it out ceeblog! @zqqcee</description><link>https://luckycc.cc/</link><language>en-us</language><follow_challenge><feedId>79193578455209984</feedId><userId>55616285853528064</userId></follow_challenge><item><title>前端秋招指北</title><link>https://luckycc.cc/interview/interview_experience/</link><guid isPermaLink="true">https://luckycc.cc/interview/interview_experience/</guid><description>昨晚完成了自己秋招很可能是最后一场面试，作为2025届秋招人，谈谈今年秋招的个人战况与感受。可能引起不适，反胃，请辩证看待，欢迎讨论。</description><pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;个人背景介绍&lt;/h1&gt;
&lt;p&gt;财经类院校211本，985硕，专业均为CS科班。&lt;/p&gt;
&lt;p&gt;简历上写了三个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实习经历：&lt;strong&gt;1段&lt;/strong&gt;含金量还可以的实习&lt;/li&gt;
&lt;li&gt;开源经历：&lt;strong&gt;1段&lt;/strong&gt;开源经历&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2段&lt;/strong&gt;加起来一年半，可写在简历上的实验室项目&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;求职意向：web前端开发&lt;/p&gt;
&lt;p&gt;投的公司：所有国内头部互联网大厂（除了某睡觉厂）&lt;/p&gt;
&lt;p&gt;战果：14投10面，3挂，&lt;strong&gt;6意向&lt;/strong&gt;，&lt;strong&gt;1排序等待回复中&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;秋招时间线&lt;/h1&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1-2月：系统刷题，准备八股&lt;/li&gt;
&lt;li&gt;3-4月：找暑期实习，高强度面试。以赛代练，培养自己的面试体感与个人表达&lt;/li&gt;
&lt;li&gt;6月：大厂开放算法岗提前批，bg较好的算法岗的同学可以着重关注，一般以“xx计划”命名&lt;/li&gt;
&lt;li&gt;7月：陆续开放秋招正式批，高强度进行笔试与测评&lt;/li&gt;
&lt;li&gt;8～10月：秋招正式批面试开始，进行高强度面试&lt;/li&gt;
&lt;li&gt;10月中旬：陆续发放意向书&lt;/li&gt;
&lt;li&gt;11～12月：谈薪，沟通并发放正式offer&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;面试准备&lt;/h1&gt;
&lt;p&gt;五个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 自我介绍。按照简历上划分的三个部分，进行面试内容的准备（实习，开源，项目）&lt;/li&gt;
&lt;li&gt;2 自我介绍中提到的项目内容&lt;/li&gt;
&lt;li&gt;3 前端相关八股（JS/CSS/HTML/网络/前端框架(React)/浏览器知识/手撕题等，具体内容整理在博客的“秋招专栏”中）&lt;/li&gt;
&lt;li&gt;4 常见面试问题（设计模式/场景题/HR面等）&lt;/li&gt;
&lt;li&gt;5 算法笔试/面试&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;自我介绍&lt;/h2&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;实习经历：挑选了三个自己独立推动落地的事情进行分享，并严格按照“背景-解决-收益”的SOP进行介绍。&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;自我介绍环节我会花费5分钟的时间，和其他秋招的同学聊过，因为他们害怕被面试官打断，所以自我介绍火急火燎一分钟就结束了。&lt;/p&gt;
&lt;p&gt;我不是很喜欢这种介绍方式，&lt;strong&gt;总觉得应该尽可能地给面试官更多context，让他有更多的问题可以在下一个环节与我讨论&lt;/strong&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;/p&gt;
&lt;p&gt;八股是我的弱项，因此我花了大多数时间准备八股。方法如下：&lt;/p&gt;
&lt;h3&gt;📖 错题本&lt;/h3&gt;
&lt;p&gt;和高中时期提升自己成绩的常用方法一样。在每一次面试后，将自己没有回答清楚的问题记录下来。我为此还在Notion中维护了一个错题本，记录自己回答错误甚至没有见过的问题。目前已整理至 &lt;a href=&quot;https://luckycc.cc/interview/&quot;&gt;秋招专栏&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;随着面试经历的增多，我将前端的八股分为了以下几类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;Javascript基础&lt;/li&gt;
&lt;li&gt;React相关知识&lt;/li&gt;
&lt;li&gt;浏览器相关知识&lt;/li&gt;
&lt;li&gt;前端工程化相关（性能优化相关也归类于此）&lt;/li&gt;
&lt;li&gt;手撕题（如实现Promise，实现防抖节流等）&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;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;当然这也有缺点，如果没有任何准备进入面试，面评容易花。我在腾讯的人才库中反复沉浮，经历10次“被捞起-被挂”后，秋招流程彻底结束。在知道腾讯上岸无望后，我把腾讯的面试当作自己的练兵场，积累面试经验。&lt;/p&gt;
&lt;p&gt;当然，如果没有面试邀约的话，还有一个方法是去牛客上找大家的面经来看，然后找到自己不会回答的问题尝试回答。一定要口述答案，不要只在心里简单默念。&lt;/p&gt;
&lt;h3&gt;⛰️ 日常积累&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;我将自己的阅读读物替换为了《React设计原理》，《你不知道的JavaScript》等书籍，高强度刷知乎，掘金等平台。&lt;/p&gt;
&lt;p&gt;这个方法获取八股知识的效率比较低，但是也有好处。对于我来说，这么做能够把“准备八股”变成一件没有那么无聊的事情，也增加了一些面试时与面试官的”&lt;strong&gt;谈资&lt;/strong&gt;“。&lt;/p&gt;
&lt;h3&gt;💡 回答八股的小技巧&lt;/h3&gt;
&lt;h4&gt;“交流”的心态&lt;/h4&gt;
&lt;p&gt;在回答面试官八股问题的时候，尽量不要用背诵的方式来回答问题，尽管这个问题你已经回答过无数次。&lt;/p&gt;
&lt;p&gt;可以想象着自己在和面试官正在交流/讨论这一项技术，甚至当做对面试官的教学。&lt;/p&gt;
&lt;p&gt;举个例子，一个很常见的前端八股：你是如何理解JS事件循环的？&lt;/p&gt;
&lt;p&gt;这个问题就可以从 JS 单线程，无法实现异步，事件循环的出现让 JS 拥有了异步能力开始讲起，而不用上来就开始介绍宏任务和微任务等等。
当然对于这个问题，还有一些比较加分的回答，比如你可以告诉面试官，由于现在任务的优先级粒度划分得更细，V8 已经不仅仅区分宏任务和微任务了，而有了更细粒度的任务划分。&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;strong&gt;探索候选人知识边界的过程&lt;/strong&gt;，毕竟你只是一个校招同学，有不了解的知识非常正常，放轻松。&lt;/p&gt;
&lt;p&gt;还有一种做法，是&lt;strong&gt;让面试官提供一些提示&lt;/strong&gt;，这种做法常用于算法/代码题没有思路的场景。&lt;/p&gt;
&lt;p&gt;我曾经在一场面试中通过面试官的提示，手撕了一个带有重试功能的 &lt;code&gt;Promise&lt;/code&gt;，写了半个小时，但也没有影响我在这场面试中获得一个不错的评价，面试说和我沟通感觉起来感觉很流畅。&lt;/p&gt;
&lt;p&gt;其实在面试过程中，候选人的“思考方式“与”沟通能力“是两个非常重要的考察因素。so feel free to ask&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;p&gt;其实对于前端来说，笔试&lt;strong&gt;并不是最重要的参考因素&lt;/strong&gt;，leetcode &lt;strong&gt;200题&lt;/strong&gt;基本够用。笔试的成绩够用即可，在录用时做个参考。&lt;/p&gt;
&lt;p&gt;值得注意的是，算法笔试OJ通常采用 &lt;strong&gt;ACM模式&lt;/strong&gt; ，也就是需要编写输入输出。&lt;/p&gt;
&lt;p&gt;因为在23年3月目睹了同门师兄姐在他们那届秋招时笔试的严峻，在23年的上旬，我就已经按题型几乎刷过一遍热门的算法题了，当时选用的语言是Java，参考的刷题网站是 &lt;a href=&quot;https://labuladong.online/algo/&quot;&gt;labuladong的算法小抄（现在收费了）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;到了24年初，我开始了第二轮刷题，本次选用的语言是JavaScript，参考的刷题网站是&lt;a href=&quot;https://programmercarl.com/&quot;&gt;代码随想录（强推）&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;如果时间紧，建议重点刷下&lt;strong&gt;二叉树&lt;/strong&gt;和&lt;strong&gt;动态规划&lt;/strong&gt;两个章节，这两个章节在算法笔面试中常考。&lt;/p&gt;
&lt;h1&gt;实习&lt;/h1&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;尽可能让自己努力的方向与小组 OKR 方向平行。&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;strong&gt;额外的产出&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;除了保证日常需求的交付外，那些额外的产出才是证明你“能力”的内容。&lt;/p&gt;
&lt;p&gt;（1）对于实习转正，这些额外的产出能够：论证你作为一个校招生具备充足的&lt;strong&gt;主动性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;（2）对于秋招，这些项目能够增价简历的“厚度”，并帮助你回答一些“你在实习期间遇见过最有挑战的事情”，“分享一个实习期间你做过印象最深刻的项目”等问题。&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;strong&gt;单独沟通&lt;/strong&gt;和&lt;strong&gt;主动宣传&lt;/strong&gt;等方式，向他人分享一些值得分享的技术或正在做的事情。这么做有以下两个好处：&lt;/p&gt;
&lt;p&gt;（1）帮助你梳理清楚这件事情的背景，做法与价值；&lt;/p&gt;
&lt;p&gt;（2）寻找一些能够在不同小组之间横向达成合作的机会；&lt;/p&gt;
&lt;h1&gt;消息与资源获取&lt;/h1&gt;
&lt;p&gt;我很喜欢 &lt;a href=&quot;https://kaiyi.cool/&quot;&gt;kaiyi&lt;/a&gt; 的一句话：“不要把和你身边共同处于秋招阶段的伙伴当作敌人，你们的信息应该共享，真正应该被视为竞争对手的是那些你压根不认识的人”。&lt;/p&gt;
&lt;p&gt;秋招期间，人与人之间的信息差体现得淋漓尽致。包括秋招前的实习招聘信息的获取，秋招时组内的基本信息，HC情况等等。&lt;/p&gt;
&lt;p&gt;常用于信息获取的渠道比较常见了，这里就不列举了。&lt;/p&gt;
&lt;p&gt;引入一下我的 &lt;a href=&quot;https://x.com/zqqcee/status/1704012477670003172&quot;&gt;Contributor学习法&lt;/a&gt;：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;虽然我有点小丑，之前一直合作的负责人不再维护G6了，后来我去找新的负责人要内推码时，没有得到回复。但是我仍然推荐这种方式，能够去结识一些社区的大佬，说不定实习机会就藏在一封邮件中👀。&lt;/p&gt;
&lt;h1&gt;介绍一个项目&lt;/h1&gt;
&lt;p&gt;在面试中我们经常需要向面试官介绍一个项目，而这个面试官很有可能没有接触过类似的业务，甚至到了交叉面时，对方还可能属于不同的岗位。这意味着你需要给一个完全没有上下文与相关知识的人，甚至非技术的人，介绍你的技术项目。&lt;/p&gt;
&lt;p&gt;“和对方讲清楚一件事情”这个能力是我非常欠缺的。首先，我不了解结构化表达的方法，表达能力比较弱。其次，在表达时，我总是默认受众与我有等量的信息上下文，在介绍一个概念或事情时，我总是默认对方和我一样已经完全具备前置知识。&lt;/p&gt;
&lt;p&gt;一味介绍&lt;strong&gt;技术深度&lt;/strong&gt;的方式显然是不可采取的，我有几点小 Tips ，读者可以做个参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于比较重要的项目，遵循：”背景介绍 =&amp;gt; 问题定义 =&amp;gt; 问题提炼（分点） =&amp;gt; 解决方案（针对问题逐一解决） =&amp;gt; 取得的收益“的 SOP进行介绍&lt;/li&gt;
&lt;li&gt;对于不重要的项目，简单概括背景，做法与收益即可&lt;/li&gt;
&lt;li&gt;不要背诵，给面试官留几个提问的气口，沟通最好以一种对话的语气向前推进&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;战后调整与心理重建&lt;/h1&gt;
&lt;p&gt;临时增补的一个部分，主要想谈谈面试中遇到恶心人的面试官如何调整心态。&lt;/p&gt;
&lt;p&gt;作为“身经百战”的面试博主，自然也是阅官无数了，简单罗列几条面试官罪行：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;面试前迟到（40%）&lt;/li&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;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;p&gt;被PUA，被否定，明明面试表现感觉良好，但还是被挂了，&lt;strong&gt;请不要把原因完全归结于自己能力不足&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;面试不通过的原因可以有很多：公司文化不符、部门HC不足、面试官心情不好，甚至会因为你太优秀了怕你不接offer为理由，终止一次面试，你的能力只是数万条理由中的一条，所以不必在意。&lt;/p&gt;
&lt;p&gt;面试是一场恋爱，不要以一个卑劣的姿态去迎合对方的喜好，选择适合自己的团队和找到适合团队发展的候选人才是这场面试的主要目的。&lt;/p&gt;
&lt;h1&gt;Offer的选择&lt;/h1&gt;
&lt;p&gt;不同级别的薪酬中值通常是个等比序列，而不是等差。例如比你高一级的人可能工资比你多一半，但高三级的人不是比你多150%，而是多238%。在这个模型里，你需要优化你的五年后，或者十年后能到达的高度。所以在比较offer时，&lt;strong&gt;不需要太关心它们之间的数字差价，而是关心去你去了之后的发展&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;感悟&lt;/h1&gt;
&lt;p&gt;2024年11月12日，双十一刚过，秋招也已经接近尾声了。&lt;/p&gt;
&lt;p&gt;秋招是一场拉锯战，不仅考察候选者的能力，还挑战着他们的耐心，意志，心态。既然是战斗，那就应该以战斗的心态来面对秋招，比如提前了解即将面试的公司，做好战前储备与战后复盘，对自己的军备水平进行准确评估等等。&lt;/p&gt;
&lt;p&gt;秋招是我们和人才市场的一场博弈，我们希望通过拿到多收割意向来论证自己的能力，拥有更多选择，公司希望使用最低的成本，来招到最适合的人才。&lt;/p&gt;
&lt;p&gt;博弈游戏，筹码多的人先赢，先亮底牌的先输。祝顺利！&lt;/p&gt;</content:encoded></item><item><title>Pull Request完整过程【记一次给antvis/G6的PR】</title><link>https://luckycc.cc/posts/antv-pr-record/</link><guid isPermaLink="true">https://luckycc.cc/posts/antv-pr-record/</guid><description>本文记录了我从发现bug，排查bug到给G6提PR，与仓库管理员沟通，最终PR被成功merge的过程。给想要参与开源，为自己喜欢的项目贡献绵薄之力的朋友提供一套完整的贡献流程参考</description><pubDate>Fri, 09 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;G6正在进行 &lt;code&gt;v4&lt;/code&gt;到 &lt;code&gt;v5&lt;/code&gt;的版本升级，发了几个Issue Hunt，因为很喜欢G6，所以想尝试帮助完成一个矩形Item的迁移。在编写测试demo的过程中，发现了G6的一个严重bug。本文记录了我从发现bug，排查bug到给G6提PR，与仓库管理员沟通，最终PR被成功merge的过程。这是我给G6的第二个PR，给想要参与开源，为自己喜欢的项目贡献绵薄之力的朋友提供一套完整的贡献流程参考。附上两次Pull Request的链接&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Doc fix ：&lt;a href=&quot;https://github.com/antvis/G6/pull/4554&quot;&gt;Fix issue#4552, another 404 not found and typo errors #4554&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Bug fix ：&lt;a href=&quot;https://github.com/antvis/G6/pull/4608&quot;&gt;Fix: “Node not found” error from ‘getNode()‘#4608&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Bug 重现&lt;/h1&gt;
&lt;p&gt;这是我在编写测试demo时，发现G6中存在的一个bug。报错信息显示：&lt;code&gt;Node not found for id: 1&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;根据字面意思，某个方法收到了 &lt;code&gt;id&lt;/code&gt;为1的节点，但是在我传入的数据中并不存在这个节点。&lt;/p&gt;
&lt;h1&gt;问题排查&lt;/h1&gt;
&lt;p&gt;我在 &lt;code&gt;Graph.getNode()&lt;/code&gt;这个方法的前后调试了很久，更奇怪的是，直接调用 &lt;code&gt;Graph.getNode(1)&lt;/code&gt;居然是能够返回节点的。由于G6的代码中写的是:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Object.&lt;/span&gt;&lt;span&gt;keys&lt;/span&gt;&lt;span&gt;(data).&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; innerModel &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; graphCore.&lt;/span&gt;&lt;span&gt;getNode&lt;/span&gt;&lt;span&gt;(id);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我在getNode之前在控制台 &lt;code&gt;console.log&lt;/code&gt;了 &lt;code&gt;id&lt;/code&gt;, 控制台输出 &lt;code&gt;1&lt;/code&gt;。这就很奇怪了，我一度怀疑是 &lt;code&gt;getNode&lt;/code&gt;这个方法出现了问题，但是 &lt;code&gt;getNode&lt;/code&gt;是一个核心方法，应该不可能出错。&lt;/p&gt;
&lt;p&gt;如果直接调用 &lt;code&gt;Graph.getNode(1)&lt;/code&gt; 能够返回节点，那么说明id就不是1，于是我输出了 &lt;code&gt;id==1&lt;/code&gt;，果不其然控制台输出 &lt;code&gt;false&lt;/code&gt;。进一步使用 &lt;code&gt;typeof&lt;/code&gt;查看 &lt;code&gt;id&lt;/code&gt;的类型，才发现id不知道怎么已经变成了 &lt;code&gt;string&lt;/code&gt;。原来使用 &lt;code&gt;Object.keys()&lt;/code&gt;生成的数组，无论 &lt;code&gt;key&lt;/code&gt;的类型是什么，统一生成为 &lt;code&gt;string&lt;/code&gt;数组。这个 &lt;code&gt;bug&lt;/code&gt;很严重啊，如果用户在数据中定义的 &lt;code&gt;id&lt;/code&gt;是 &lt;code&gt;number&lt;/code&gt;类型，那么将无法获取到这个 &lt;code&gt;Node&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;到这里，问题就定位完毕了。&lt;/p&gt;
&lt;h1&gt;解决方案&lt;/h1&gt;
&lt;p&gt;其实要解决这个问题很简单，有以下三种方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在用户的id为 &lt;code&gt;number&lt;/code&gt;类型时，使用 &lt;code&gt;Number(id)&lt;/code&gt;进行一次转换，而在用户 &lt;code&gt;id&lt;/code&gt;为 &lt;code&gt;string&lt;/code&gt;类型时，不做任何处理&lt;/li&gt;
&lt;li&gt;修改 &lt;code&gt;getNode()&lt;/code&gt;，使他能够识别用户传入数据中 &lt;code&gt;id&lt;/code&gt;的类型&lt;/li&gt;
&lt;li&gt;在文档中强制限制用户输入 &lt;code&gt;string&lt;/code&gt;类型，并且使用类型检查将用户输入的data限制为string&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三种方法首先排除第二种，因为 &lt;code&gt;getNode()&lt;/code&gt;是一个核心方法，是从 &lt;code&gt;antv&lt;/code&gt;的核心代码仓中 &lt;code&gt;import&lt;/code&gt;过来的一个方法，找不到修改的入口&lt;/p&gt;
&lt;p&gt;我这里选择了第一种方法，并在PR中提示了，如果不做修改的话需要在文档中明确标注 &lt;code&gt;id&lt;/code&gt;必须为 &lt;code&gt;string&lt;/code&gt;类型&lt;/p&gt;
&lt;p&gt;在解决这个问题的时候，我还考虑到了一种情况，如果用户比较调皮，&lt;strong&gt;输入的 &lt;code&gt;id&lt;/code&gt;中又有 &lt;code&gt;string&lt;/code&gt;类型，又有 &lt;code&gt;number&lt;/code&gt;类型&lt;/strong&gt;应该怎么解决呢？这里我采用了添加 &lt;code&gt;try-catch&lt;/code&gt;代码块来进行解决。&lt;/p&gt;
&lt;h2&gt;old version&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// &apos;id&apos; variable is always string in here, but one in user data is number, possibly.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Object.&lt;/span&gt;&lt;span&gt;keys&lt;/span&gt;&lt;span&gt;(data).&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; innerModel &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; graphCore.&lt;/span&gt;&lt;span&gt;getNode&lt;/span&gt;&lt;span&gt;(id);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; relatedEdgeInnerModels &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; graphCore.&lt;/span&gt;&lt;span&gt;getRelatedEdges&lt;/span&gt;&lt;span&gt;(id);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;new version&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Object.&lt;/span&gt;&lt;span&gt;keys&lt;/span&gt;&lt;span&gt;(data).&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	let&lt;/span&gt;&lt;span&gt; innerModel;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		innerModel &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; graphCore.&lt;/span&gt;&lt;span&gt;getNode&lt;/span&gt;&lt;span&gt;(id);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (e) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		innerModel &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; graphCore.&lt;/span&gt;&lt;span&gt;getNode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Number&lt;/span&gt;&lt;span&gt;(id));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	let&lt;/span&gt;&lt;span&gt; relatedEdgeInnerModels;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		relatedEdgeInnerModels &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; graphCore.&lt;/span&gt;&lt;span&gt;getRelatedEdges&lt;/span&gt;&lt;span&gt;(id);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (error) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		relatedEdgeInnerModels &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; graphCore.&lt;/span&gt;&lt;span&gt;getRelatedEdges&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Number&lt;/span&gt;&lt;span&gt;(id));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;并且我在我的PR底下，做了如下说明，来询问这么做是否合理：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An error is throwed from this function ‘getNode()’; msg: “Node not found for id: 1”
After testing, I found that ‘id’ variable is always string after &lt;code&gt;Object.keys(update).forEach((id)=&amp;gt;{...})&lt;/code&gt;, but one in user data is number, possibly.
I tried adding a &lt;code&gt;try-catch&lt;/code&gt; block to fix this bug, but &lt;strong&gt;it doesn’t seem very reasonable to do so&lt;/strong&gt;. I guess you can restrict the user to set the &lt;code&gt;id&lt;/code&gt; to a string type in the document, or force the &lt;code&gt;id&lt;/code&gt; to a string type in the &lt;code&gt;transformer data&lt;/code&gt; layer to avoid this error.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;code&gt;getNode()&lt;/code&gt;方法抛出了一个异常； 报错信息为：“找不到 id为1的节点“
经过测试，我发现 &lt;code&gt;id&lt;/code&gt;变量在 &lt;code&gt;Object.keys(update).forEach((id)=&amp;gt;{...})&lt;/code&gt;之后总是字符串类型，但是这个 &lt;code&gt;id&lt;/code&gt;变量在用户数据中很有可能是数字类型。
我尝试添加一个 &lt;code&gt;try-catch&lt;/code&gt; 块来修复这个错误，但&lt;strong&gt;这样做似乎不太合理&lt;/strong&gt;。 我想你们可以在文档中说明：限制 &lt;code&gt;id&lt;/code&gt; 为字符串类型，或者在 &lt;code&gt;transformer data&lt;/code&gt; 层强制将 &lt;code&gt;id&lt;/code&gt; 设置为字符串类型来避免这个错误。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;收到回复&lt;/h1&gt;
&lt;p&gt;很快，我收到了仓库管理员&lt;strong&gt;十吾&lt;/strong&gt;的回复，她回复了一个👍，我好开心，我问她这是可以接受的吗，如果是的话，需不需要重新创建一个PR来进行提交（因为我一开始提交的PR有其他修改，但是另外的修改无法被merge）。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h1&gt;PR提交完整过程&lt;/h1&gt;
&lt;p&gt;这一部分记录完整的PR提交过程，其中包含了我遇到的问题，一并做陈述并给出解决方案。因为这是我第二次给开源仓库做贡献，所以一些看起来很简单的细节我也记录在这里，帮后面的同学少踩一些坑。&lt;/p&gt;
&lt;h2&gt;fork仓库 &amp;amp; clone代码仓&lt;/h2&gt;
&lt;p&gt;直接fork，选仅fork默认分支即可。fork仓库后，在自己的github主页就能看到一个一摸一样的代码仓了。这一步注意，是要clone自己fork后的代码仓，比如我需要clone的地址是：&lt;code&gt;https://github.com/zqqcee/G6.git&lt;/code&gt;,这里 &lt;code&gt;zqqcee&lt;/code&gt;是自己的用户名，不要clone错了。&lt;/p&gt;
&lt;h2&gt;添加upstream&lt;/h2&gt;
&lt;p&gt;这一步的目的是将 &lt;code&gt;antvis&lt;/code&gt;的源仓库添加为上游仓库，不然我们无法同步它们的更新。运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git remote add upstream &quot;https://github.com/antvis/G6.git&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行完毕后，输入 &lt;code&gt;git remote -v&lt;/code&gt; ，能够看到&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;origin:xxxx
origin:xxxx
upstream:xxxx
upstream:xxxx&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;fetch 新分支&lt;/h2&gt;
&lt;p&gt;由于我是给&lt;strong&gt;v5分支&lt;/strong&gt;提的PR，因此我需要先&lt;strong&gt;fetch v5分支&lt;/strong&gt;。运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git fetch upstream/v5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入 &lt;code&gt;git branch -a&lt;/code&gt;就可以看到有一个红色的分支 &lt;code&gt;upstream/v5&lt;/code&gt;，这说明已经fetch成功了&lt;/p&gt;
&lt;p&gt;下一步，我们就需要把这个分支的内容在本地创建，并进行修改。&lt;/p&gt;
&lt;h2&gt;创建新分支&lt;/h2&gt;
&lt;p&gt;这一步在我执行的时候有一个很大的坑：我在master分支上直接运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git checkout -b v5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git rebase xxx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果出了&lt;strong&gt;一堆冲突&lt;/strong&gt;，后来才知道是&lt;strong&gt;我的v5分支是从master分支上创建的，而不是从远程拉过来的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;应该输入：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git checkout -b origin/v5 upstream/v5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步的意思是从 &lt;code&gt;upstream/v5&lt;/code&gt;分支创建一个 &lt;code&gt;origin/v5&lt;/code&gt;分支。&lt;/p&gt;
&lt;p&gt;到这里还没结束，因为这个 &lt;code&gt;origin/v5&lt;/code&gt;分支是我们从upstream中拉取出来的，我的习惯是要在这个分支上再新建一个分支做开发，分支名也有一些含义，于是接着运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git checkout -b v5-fix#NodeNotFound&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;完成修改（注意commit规范）&lt;/h2&gt;
&lt;p&gt;写完代码后注意自己的commit规范，每一个commit都要让别人能看懂，不要全部修改完再做提交。这里我把每一个修改的含义都分得比较清楚，如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;bug 重现 commit&lt;/li&gt;
&lt;li&gt;bug 修复 commit&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;push到个人仓库&lt;/h2&gt;
&lt;p&gt;在这一步我遇到了大麻烦，由于G6发布了 &lt;code&gt;issue hunt&lt;/code&gt;,因此这里我push到个人仓库时，由于我的**&lt;code&gt;personal token&lt;/code&gt;没有包含workflow，&lt;strong&gt;因此push不成功。报错：&lt;/strong&gt;“refusing to allow a Personal Access Token to create or update workflow &lt;code&gt;.github/workflows/build.yml&lt;/code&gt; without &lt;code&gt;workflow&lt;/code&gt; scope”**&lt;/p&gt;
&lt;p&gt;这一步正确的解决方案是重新创建一个Token并push，但是我为了省事，直接把workflow删掉了。结果PR就没有被合，并且收到了这个comment：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;对于这个问题，解决方案我也记录在此处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;首先，创建一个token,勾选workflow，这一步在网上有很多教程，跟着做就好了，这里不做过多赘述&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接着，重新设置 &lt;code&gt;origin&lt;/code&gt;。运行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;git remote remove origin&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;git remote add origin https://{token}@github.com/zqqcee/G6.git&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 &lt;code&gt;{token}&lt;/code&gt;替换为刚刚创建的带workflow的token&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后，重新push就能成功了&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;创建Pull Request&lt;/h2&gt;
&lt;p&gt;到fork的仓库中，push成功后，仓库中会显示有一个新的分支。然后点击 &lt;code&gt;Pull Request&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;创建一个新的 &lt;code&gt;Pull Request&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这一步没什么好说的，重点是要选对你要修改的分支&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;填写PR信息&lt;/h2&gt;
&lt;p&gt;PR信息非常关键，必须非常清楚地说明你为什么要创建这个PR ，以及这个PR修复了什么问题。这里直接贴上我的PR 说明，供参考。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;以上就是全部的解决过程了，很开心能为G6做了贡献，希望有机会能加入AntV团队，也希望自己能为更多仓库创建更优秀的PR～！&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title>新版博客开发小记</title><link>https://luckycc.cc/posts/astro-blog-dev-handbook/</link><guid isPermaLink="true">https://luckycc.cc/posts/astro-blog-dev-handbook/</guid><description>出于定制化的考虑，我决定在2024年的国庆小长假实现一个积攒许久的心愿：将原来使用Hexo搭建的博客迁移至Astro，实现博客内容与样式自由。本文记录了在新版博客中的折腾，设计与创造</description><pubDate>Mon, 14 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;/p&gt;
&lt;h1&gt;背景&lt;/h1&gt;
&lt;p&gt;其实写博客这件事，在很早就开始了。&lt;/p&gt;
&lt;h2&gt;Timeline&lt;/h2&gt;
&lt;p&gt;2020-01-11，我在CSDN上发布了我的第一篇文章，当时是大二的下学期，因为想进入某位导师的智能媒体与数据挖掘实验室，学习了使用python进行网络爬虫。这一篇博客只贴上了代码，没有其他的内容。&lt;/p&gt;
&lt;p&gt;真正意义上的第一篇博客是在 2020-09-27 写下的，是一篇介绍Sublime Text3的文章，&lt;a href=&quot;https://blog.csdn.net/RRie1/article/details/108832279&quot;&gt;Sublime Text3——高效，美观，强大&lt;/a&gt;。仍然发布在CSDN平台上。&lt;/p&gt;
&lt;p&gt;在2022-07-19，我被保送到中南大学读研，研究方向是数据可视化，&lt;strong&gt;开始和前端打交道&lt;/strong&gt;，萌生出自己搭建一个博客的想法。&lt;/p&gt;
&lt;p&gt;同期，CSDN做的越来越差，&lt;strong&gt;广告/垃圾信息/错误解答&lt;/strong&gt; 满天飞，我彻底决定要将博客迁移至自建平台上。&lt;/p&gt;
&lt;p&gt;选择了当时比较主流的一种 “低代码/无代码” 方案： &lt;strong&gt;Hexo+Github Pages+Github图床&lt;/strong&gt; 。选择了NexT主题，在当时看来，效果还蛮不错的。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;在Hexo这版博客上，我也折腾了许多东西，比如：添加&lt;a href=&quot;https://github.com/zqqcee/ipad-cursor-hexo&quot;&gt;ipad-cursor-hexo&lt;/a&gt;，ipad-cursor是&lt;strong&gt;CatJuice&lt;/strong&gt;的作品，我做了个胶水层在hexo中使用，感兴趣的话可以看看&lt;a href=&quot;https://luckycc.cc/posts/ipad-cursor-in-hexo/&quot;&gt;这篇文章：在博客中应用ipad-cursor，@Hexo(Theme:NexT)&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;使用Hexo搭建的博客，发布文章非常方便，集成了非常完整的CI/CD，发布文章后通过github-action自动部署至github.io上。但是缺点也非常明显：&lt;strong&gt;定制化成本非常高&lt;/strong&gt;，添加一个页面都需要折腾好久，更别说添加一些动画或定制某个板块了。&lt;/p&gt;
&lt;p&gt;2024-10-01，在2024年国庆，我结束了暑期实习，准备开启孤独又漫长的研三生活。&lt;/p&gt;
&lt;p&gt;我希望用更多文字来记录这段时光，苦于hexo难以进行功能扩展，github pages 国内的访问速度也受限，我决定使用Astro重新搭建个人博客，并将其部署至国内云服务器上，实现&lt;strong&gt;博客内容与样式自由&lt;/strong&gt;的同时，也享受一次&lt;strong&gt;折腾与创造的乐趣&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;本文用于记录新版博客中的开发经历与体验。&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;技术栈选型&lt;/h1&gt;
&lt;p&gt;技术栈选型上，其实是没有什么太多选择的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;framework：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; - 内容驱动，搭建方便&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hono.dev/&quot;&gt;Hono&lt;/a&gt; - 用来搭建评论系统的后端，轻量简洁。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://orm.drizzle.team/&quot;&gt;Drizzle&lt;/a&gt; + &lt;a&gt;Postgres&lt;/a&gt; - ORM + 数据库存储&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bark.day.app&quot;&gt;Bark&lt;/a&gt; - 通知推送工具，用于在受到新评论时提醒自己审批&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;style：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://tailwindcss.com/&quot;&gt;tailwindcss&lt;/a&gt; - 主要原因是我没有深入使用过，尝鲜体验一下。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;用下来之后发现确实挺方便的，但是代码样式也很难管理。“带鱼屏都看不完你写的 class”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://magicui.design/&quot;&gt;Magic-UI&lt;/a&gt; - 黑鸦推荐，一个基于 framer-motion 二次封装的动画库。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ui.shadcn.com/&quot;&gt;shadcn&lt;/a&gt; - 选用的理由仍然是没有使用过，想体验一下。这个组件库除了 NavBar 比较难用外，其他方面都很不错&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;部署: docker + &lt;a href=&quot;https://caddyserver.com/&quot;&gt;caddyserver&lt;/a&gt;，SkyWT 推荐的部署方案，自动支持 https，简单易用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务器：阿里云轻量服务器serve博客，OSS做自建图床&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;板块规划&lt;/h1&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;/ul&gt;
&lt;h1&gt;UI/UX 设计&lt;/h1&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;右侧是Cee Zhou的花体签名。&lt;/p&gt;
&lt;p&gt;制作的流程整体follow了&lt;a href=&quot;https://doctorwu.me/&quot;&gt;Doctor Wu的博客&lt;/a&gt;，其中有一篇&lt;a href=&quot;https://doctorwu.me/posts/animation-signature-zh&quot;&gt;动画签名的制作&lt;/a&gt; ，介绍了如何制作一个动态签名。它分为以下几步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在&lt;a href=&quot;https://signaturely.com/online-signature/type/&quot;&gt;Signaturely&lt;/a&gt;，找到一张自己满意的签名图片&lt;/li&gt;
&lt;li&gt;使用 &lt;a href=&quot;https://inkscape.org/&quot;&gt;Ink space&lt;/a&gt; 扣出这张图像的 svg path&lt;/li&gt;
&lt;li&gt;创建 svg CSS 动画，可以看这篇： &lt;a href=&quot;https://jakearchibald.com/2013/animated-line-drawing-svg&quot;&gt;Animated line drawing in SVG&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;霓虹灯&lt;/h2&gt;
&lt;p&gt;我用codepen.io存放了这个效果的源代码：&lt;a href=&quot;https://codepen.io/zqqcee-the-bold/pen/KKjjorx&quot;&gt;The neon light&lt;/a&gt;，感兴趣可以看看。&lt;/p&gt;
&lt;p&gt;原理实际上是给三个矩形加一些不规则的 &lt;code&gt;clip-path&lt;/code&gt;，并且为他们添加一个 &lt;code&gt;blur&lt;/code&gt;的过滤，使用动画来变更这三个矩形的 &lt;code&gt;scale&lt;/code&gt; 和 &lt;code&gt;opacity&lt;/code&gt; 。效果仍然参考了&lt;a href=&quot;https://doctorwu.me/&quot;&gt;Doctor Wu的博客&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h1&gt;部署方式的更迭&lt;/h1&gt;
&lt;h2&gt;部署至github.io&lt;/h2&gt;
&lt;p&gt;2024-10-05，我完成了第一版博客的开发。&lt;/p&gt;
&lt;p&gt;当时正是秋招面试的收尾阶段，我的简历上放了个人博客的地址。因此我先将新开发好的博客，部署到zqqcee.github.io 上，替换原来的最初版的博客。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Deploy to GitHub Pages&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  push&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    branches&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;permissions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  contents&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; read&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  pages&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; write&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  id-token&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; write&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;jobs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  build&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    runs-on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Checkout your repository using git&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; actions/checkout@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Set up Node.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; actions/setup-node@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          node-version&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Setup pnpm&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; pnpm/action-setup@v2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          version&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 8.7.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Install&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        run&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; pnpm install&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        run&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; pnpm run astro build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Upload Pages Artifact&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; actions/upload-pages-artifact@v3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          path&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;./dist/&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  deploy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    needs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    runs-on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    environment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; github-pages&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      url&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ${{ steps.deployment.outputs.page_url }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Configure Pages&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; actions/configure-pages@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Deploy to GitHub Pages&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; deployment&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; actions/deploy-pages@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但 Github Pages 部署的内容有一个问题，国内访问速度受限。&lt;/p&gt;
&lt;p&gt;在完成博客基础功能开发后的第二天，我在阿里云上购买了一台轻量服务器，一个域名：luckycc.cc。&lt;/p&gt;
&lt;p&gt;并打算将自己的博客部署在这台服务器上，提高国内用户的访问速度。&lt;/p&gt;
&lt;h2&gt;方案一：docker内caddy&lt;/h2&gt;
&lt;p&gt;在此之前我对 Docker 的了解仅仅停留在：可移植，易部署，环境隔离。国庆期间室友刚好在寝室，拖着他和我一起折腾。这是我们瞎捣鼓想出来的第一版部署方案&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;采用 docker+caddyserver 部署。&lt;/p&gt;
&lt;p&gt;在完成本地提交之后，在服务器拉取最新的代码，然后使用docker进行部署，整个流程为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;本地提交更改&lt;/li&gt;
&lt;li&gt;云服务器拉取更改&lt;/li&gt;
&lt;li&gt;docker build&lt;/li&gt;
&lt;li&gt;docker start&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;docker暴露80和443两个端口，用户访问&lt;code&gt;https://luckycc.cc&lt;/code&gt;，其实就是访问了服务器上的:443端口，443端口启动了一个 docker 容器，会直接访问到容器内的443端口。容器内的443端口是 caddy 的 fileserver ，会访问到产物。&lt;/p&gt;
&lt;p&gt;然而在 2024-11，我的网站突然崩了&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;在看了 caddy 的 log 之后，发现是对同一域名申请了太多次证书导致证书失效无法再次申请。因为每次在修改代码之后，我都需要把docker重新 build ，重新 run ，而且没有把 caddy 的证书持久化存储。&lt;/p&gt;
&lt;p&gt;我开始思考这种部署方案的合理性。这种方案有一个很大的问题：服务器只有 80 和 443 可用。正确的部署方案应该是宿主机上用caddy来反向代理所有的请求。&lt;/p&gt;
&lt;p&gt;因为马上要开始开发博客的评论系统，迫不得已需要更换部署方案&lt;/p&gt;
&lt;h2&gt;方案二：主机caddy&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;主机上使用一个 Global Caddy 来反向代理所有服务才是正确的部署方式。&lt;/p&gt;
&lt;p&gt;在这种部署方案下，每次博客有更新，我需要先拉去到更新，再在本地build生成产物，然后使用 Caddy 来代理这个产物。&lt;/p&gt;
&lt;h2&gt;方案三： 主机caddy + github action&lt;/h2&gt;
&lt;p&gt;方案二中这种本地 build 的方法可以让 Github Action 来代理。于是产生了第三版部署方案&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;在这一版方案中，github action 会&lt;strong&gt;在 main 分支有新的提交时&lt;/strong&gt;，运行。build产物并将产物推送至 release分支。&lt;/p&gt;
&lt;p&gt;这样我在主机上就不需要本地构建了，只需要拉取release分支获取最终更改即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;# workflow.yml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Deploy to GitHub Pages&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  push&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    branches&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;permissions&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  contents&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; write&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  pages&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; write&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  id-token&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; write&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;jobs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  build&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    runs-on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    env&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      PUBLIC_API&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ${{vars.PUBLIC_API}}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Checkout your repository using git&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; actions/checkout@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Set up Node.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; actions/setup-node@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          node-version&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Setup pnpm&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; pnpm/action-setup@v2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          version&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 8.7.0&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Install&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        run&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; pnpm install&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        run&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; pnpm run astro build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Upload Pages Artifact&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; actions/upload-pages-artifact@v3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          path&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;./dist/&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Deploy to pages branch&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; peaceiris/actions-gh-pages@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        with&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          github_token&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          publish_dir&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ./dist&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          publish_branch&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; release&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  deploy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    needs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    runs-on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ubuntu-latest&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    environment&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; github-pages&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      url&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; ${{ steps.deployment.outputs.page_url }}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    steps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Configure Pages&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; actions/configure-pages@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span&gt; name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Deploy to GitHub Pages&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; deployment&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        uses&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; actions/deploy-pages@v4&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;评论系统&lt;/h1&gt;
&lt;p&gt;虽然我的博客可能短期内不会有太多浏览量，但我还是做了评论功能。&lt;/p&gt;
&lt;p&gt;评论是联系的桥梁，很高兴你在这儿留下痕迹。&lt;/p&gt;
&lt;p&gt;在每一篇文章底部，都可以发表评论。发表评论需要提供你的昵称，邮箱，个人网站（是可选的，点击头像可以跳转）。&lt;/p&gt;
&lt;p&gt;对于头像服务，使用了&lt;a href=&quot;https://unavatar.io/#/&quot;&gt;unavatar.io&lt;/a&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果你填写了QQ邮箱（使用QQ号而非邮箱别名），将显示QQ头像&lt;/li&gt;
&lt;li&gt;如果邮箱设置了 Gravatar 头像，将显示 Gravatar 头像&lt;/li&gt;
&lt;li&gt;否则，将显示 unavatar.io 提供的默认头像&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;博客没有做登录功能，因此所有的评论都将通过审核才会被展示。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;评论的后端接入了 &lt;a href=&quot;https://bark.day.app/&quot;&gt;bark&lt;/a&gt; ，每有一位读者进行一次评论，都会像我推送一条通知，我会第一时间审核并通过。&lt;/p&gt;</content:encoded></item><item><title>实现autoZoom(),画布自适应放缩并居中@D3.js-v5</title><link>https://luckycc.cc/posts/autoZoom/</link><guid isPermaLink="true">https://luckycc.cc/posts/autoZoom/</guid><description>一张节点链接图，实现一个自适应放缩方法，使画布按照屏幕的尺寸进行放缩，并将元素居中展示。</description><pubDate>Sun, 20 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;需求陈述：&lt;/h3&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;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;首先，创建一个 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;标签（图中为灰绿色），长宽与用户界面/组件的长宽相同。这个 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;只是一个包裹的容器，一般是不直接在其中放置图元的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接着，在 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;内部创建一个 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;标签（图中为黑色），我们真正需要绘制的图元，都会放置在这个 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;标签中。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;标签绑定 &lt;code&gt;d3.zoom()&lt;/code&gt;事件，并将这个 &lt;code&gt;zoom&lt;/code&gt;事件的 &lt;code&gt;transform&lt;/code&gt;对象，应用在 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;中&lt;/p&gt;
&lt;p&gt;只要理解了最后一步，就理解了这整个流程。为什么要把 &lt;code&gt;d3.zoom()&lt;/code&gt;绑定在外部的 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;标签上呢？我们需要设想一个场景：假设我们把zoom事件绑定在了内部的 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;标签上，那么当用户将 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;标签全部拖动到 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;外部时，就没办法拖回来了。因为此时用户鼠标已经无法选中 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;标签了。比如下面这种情况：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;因此，为了避免用户将画布拖走后无法拖回来，我们应该设置一个“静止”的窗口，将拖动和放缩事件绑定在这上面，并且将这个事件作用来这个静止窗口内部的元素上。这里的静止窗口就是 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;,而事件作用的元素就是 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;，这也解释了为什么要选用这种嵌套的形式。&lt;/p&gt;
&lt;p&gt;理解了这点，代码就很好写了。我们只需要向放缩的函数中传入外部 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;的id，内部 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;的id，&lt;code&gt;zoomObj&lt;/code&gt;即可。这里还可以传入 &lt;code&gt;padding&lt;/code&gt;，和 &lt;code&gt;duration&lt;/code&gt;，设置画布的左右间隙和补间动效。&lt;/p&gt;
&lt;h3&gt;代码实现&lt;/h3&gt;
&lt;h5&gt;设置用户摁下 &lt;code&gt;ctrl&lt;/code&gt;键，就自适应放缩并居中const zoomObj = d3.zoom().scaleExtent([1 / 50, 2]);&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;document.&lt;/span&gt;&lt;span&gt;onkeydown&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; (e.keyCode &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; 17&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		autoZoom&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			zoomObj, &lt;/span&gt;&lt;span&gt;//传入zoomObj&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&apos;&lt;/span&gt;&lt;span&gt;svgContainer&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;//&amp;lt;svg&amp;gt;的id&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&apos;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;//&amp;lt;g&amp;gt;的id&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				row&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				col&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			}, &lt;/span&gt;&lt;span&gt;//间隙参数（自定）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			1000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// 补间时长（自定）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;&lt;code&gt;autoZoom()&lt;/code&gt;函数实现&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//autoZoom() function body&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @param&lt;/span&gt;&lt;span&gt; zoomObj&lt;/span&gt;&lt;span&gt;: 放缩对象，设置了放缩比率&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @param&lt;/span&gt;&lt;span&gt; svgContainerId&lt;/span&gt;&lt;span&gt;: 容器 &amp;lt;svg&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @param&lt;/span&gt;&lt;span&gt; svgBodyId&lt;/span&gt;&lt;span&gt;: 画布 &amp;lt;g&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @param&lt;/span&gt;&lt;span&gt; marginParam&lt;/span&gt;&lt;span&gt;: 间隙参数（自定义）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  @param&lt;/span&gt;&lt;span&gt; duration&lt;/span&gt;&lt;span&gt;: 补间时长&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; autoZoom&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;zoomObj&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;svgContainerId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;svgBodyId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;marginParam&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;duration&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; svgContainer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`#&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;svgContainerId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; svgBody &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`#&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;svgBodyId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;svgContainer) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; viewBox &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; svgBody.&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;getBBox&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;//必须用d3.select,才有getBox，获取到长和宽&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//svg（它是静止的）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; containerWidth &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; svgContainer.clientWidth; &lt;/span&gt;&lt;span&gt;//svg标签的宽&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; containerHeight &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; svgContainer.clientHeight; &lt;/span&gt;&lt;span&gt;//svg标签的高&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// margin setting&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; rowMargin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; marginParam.row;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; colMargin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; marginParam.col;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//计算放缩倍数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; scale &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		(containerWidth &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; rowMargin) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; viewBox.width,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		(containerHeight &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; colMargin) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; viewBox.height,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//计算如果要居中，画布需要的偏移量&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; offsetX &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (containerWidth &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; rowMargin) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; (viewBox.x &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; viewBox.width &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; scale;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; offsetY &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (containerHeight &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; colMargin) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; -&lt;/span&gt;&lt;span&gt; (viewBox.y &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; viewBox.height &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; scale;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// d3.zoomIdentity:缩放参数，返回Transform{k:1,x:0,y:0}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; t &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.zoomIdentity.&lt;/span&gt;&lt;span&gt;translate&lt;/span&gt;&lt;span&gt;(offsetX &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; rowMargin &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;, offsetY).&lt;/span&gt;&lt;span&gt;scale&lt;/span&gt;&lt;span&gt;(scale);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//计算完毕得到放缩参数t，&amp;lt;svg&amp;gt;标签调用zoomObj和计算好的t&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	d3.&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`#&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;svgContainerId&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;transition&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;duration&lt;/span&gt;&lt;span&gt;(duration).&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(zoomObj.transform, t);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>【趟坑记录】d3.zoom()的正确使用姿势 @d3.v7</title><link>https://luckycc.cc/posts/d3-zoom/</link><guid isPermaLink="true">https://luckycc.cc/posts/d3-zoom/</guid><description>在开发一个D3应用的时候遇到了一个zoom相关的问题，记录解决思路与方案</description><pubDate>Fri, 20 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;【趟坑记录】&lt;code&gt;d3.zoom()&lt;/code&gt;的正确使用姿势 @d3.v7&lt;/h1&gt;
&lt;p&gt;在开发一个D3应用的时候遇到了一个 &lt;code&gt;zoom&lt;/code&gt;相关的问题，记录解决思路与方案&lt;/p&gt;
&lt;h3&gt;问题重现&lt;/h3&gt;
&lt;p&gt;最近在开发一个D3应用的时候遇到了一个 &lt;code&gt;zoom&lt;/code&gt;相关的问题，应用里有一个功能叫&lt;strong&gt;全景聚焦&lt;/strong&gt;。我们都知道画布由两个标签组成（见&lt;a href=&quot;https://zqqcee.github.io/2023/03/24/3423a90bb58e/&quot;&gt;实现autoZoom(),画布自适应放缩并居中@D3.js-v5&lt;/a&gt;），最外层的是固定视口 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;，一般将 &lt;code&gt;zoom&lt;/code&gt;事件绑定在 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;上；内层是具体的画布，是一个 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;标签，在 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;中的放缩与平移操作都作用在 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;上，修改 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;的 &lt;code&gt;transform&lt;/code&gt;属性。这么做是为了避免用户将 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;元素拖动到窗口之外后丢失拖动焦点，无法将其拖回。而如果使 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;不动，&lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;被拖动，那么拖动焦点就不会丢失，用户将 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;元素移动至视口外后，还能将其拖回来。&lt;/p&gt;
&lt;p&gt;我之前习惯这么写拖动平移：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; svg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;#viewport&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, width).&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, height);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; g &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; svg.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;container&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, width).&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, height);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;svg.&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	d3.&lt;/span&gt;&lt;span&gt;zoom&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;zoom&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const&lt;/span&gt;&lt;span&gt; transform &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; `translate(&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;e.transform.x&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;e.transform.y&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;) scale(&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;e.transform.k&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		g.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, transform);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在一些业务场景中，往往需要对 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;元素进行特定的平移与放缩。如：自动缩放至视口中央，放大至当前的1.5倍。然而，在其他直接地方修改了 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt; 的 &lt;code&gt;‘transform’&lt;/code&gt;属性后，如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; offsetX &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; offsetY &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 10&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;g.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;`translate(&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;offsetX&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;offsetY&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)`&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;，问题就出现了，如下：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;可以看到，在设置了特定的 &lt;code&gt;&apos;transform&apos;&lt;/code&gt;后，再进行拖动，会出现瞬移。&lt;/p&gt;
&lt;h3&gt;原因分析&lt;/h3&gt;
&lt;p&gt;因为监听的 &lt;code&gt;zoom&lt;/code&gt;事件是通过 &lt;code&gt;e.transform&lt;/code&gt;来进行放缩的。而在修改 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;元素的 &lt;code&gt;‘transform’&lt;/code&gt;属性为一个特定值后，再进行拖动，会从上一次的 &lt;code&gt;e.tranform&lt;/code&gt;值开始修改，因此会出现错误。&lt;/p&gt;
&lt;p&gt;举例说明：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户拖动，&lt;code&gt;e.transform&lt;/code&gt;的数值修改为了 &lt;code&gt;transform_1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;有一个自动放缩函数 &lt;code&gt;autoZoom&lt;/code&gt;，将 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;的 &lt;code&gt;&apos;transform&apos;&lt;/code&gt;修改为了 &lt;code&gt;transform_2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用户再次进行拖动，&lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;的 &lt;code&gt;&apos;transform&apos;&lt;/code&gt;会从 &lt;code&gt;transform_1&lt;/code&gt;开始修改，因此会出现从 &lt;code&gt;transform_2&lt;/code&gt;到 &lt;code&gt;transform_1&lt;/code&gt;的瞬移。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;得知原因之后，解决方案也非常明了。就是在任何需要进行放缩平移的地方，都将 &lt;code&gt;transform&lt;/code&gt;进行缓存，下一次再需要进行放缩平移操作时，从上一次的 &lt;code&gt;transform&lt;/code&gt;开始进行更改即可。&lt;/p&gt;
&lt;p&gt;一开始我想的解决方案是在每次鼠标拖动时都记录一个偏移量，但是这个偏移量比较难获取，心想 &lt;code&gt;d3&lt;/code&gt;这么大个库应该不至于用这么蠢的办法，应该有更好用的方案。&lt;/p&gt;
&lt;p&gt;查了一下官方的API，发现了一个叫 &lt;code&gt;zoomTransform(node)&lt;/code&gt;的接口，这个接口传入的是一个 &lt;code&gt;HTML node&lt;/code&gt;，需要用 &lt;code&gt;d3.select(xx).node()&lt;/code&gt;来获得，可以获取这个 &lt;code&gt;node&lt;/code&gt;的放缩数据。官方文档是这么说的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Internally, an element’s transform is stored as &lt;em&gt;element&lt;/em&gt;.__zoom; however, you should use this method rather than accessing it directly. If the given &lt;em&gt;node&lt;/em&gt; has no defined transform, returns the transform of the closest ancestor, or if none exists, the identity transformation&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;在内部，元素的变换存储为 &lt;strong&gt;element.__zoom&lt;/strong&gt;；但是，&lt;strong&gt;您应该使用此方法（指的是zoomTransform）而不是直接访问它&lt;/strong&gt;。如果给定节点没有定义的变换，则返回最近祖先的变换，或者如果不存在，则返回恒等变换。返回的变换表示以下形式的二维变换矩阵(略)：&lt;/p&gt;
&lt;p&gt;These properties should be considered read-only; instead of mutating a transform, use &lt;a href=&quot;https://d3js.org/d3-zoom#transform_scale&quot;&gt;&lt;em&gt;transform&lt;/em&gt;.scale&lt;/a&gt; and &lt;a href=&quot;https://d3js.org/d3-zoom#transform_translate&quot;&gt;&lt;em&gt;transform&lt;/em&gt;.translate&lt;/a&gt; to derive a new transform.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;这些属性应被视为只读；使用transform.scale和transform.translate来派生新的变换，而不是改变变换。（下文将介绍如何派生新的变换）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;进一步查看了源码，发现在 &lt;code&gt;svg.call(zoom)&lt;/code&gt;这个操作后，&lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;这个HTML node就会绑上一个 &lt;code&gt;__zoom&lt;/code&gt; 属性，这个 &lt;code&gt;__zoom&lt;/code&gt;属性记录的是 &lt;code&gt;transform&lt;/code&gt;参数，也就是我们对 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;进行的放缩平移变换。为此我还特定打印了一下，发现确实如此：&lt;/p&gt;
&lt;p&gt;![log的结果](/Users/zqqcee/Library/Application Support/typora-user-images/image-20230720100136154.png)&lt;/p&gt;
&lt;p&gt;那现在事情就变得很简单了，可以转变一下思路。之前我一直希望能够在 &lt;code&gt;autoZoom()&lt;/code&gt;之后，获得 &lt;code&gt;&quot;zoom&quot;&lt;/code&gt;事件的偏移量，使得我能够接着这个 &lt;code&gt;&apos;transform&apos;&lt;/code&gt;值修改。那么既然我无法获得偏移量，可以尝试在 &lt;code&gt;autoZoom()&lt;/code&gt;方法中不要直接修改 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;的 &lt;code&gt;&apos;transform&apos;&lt;/code&gt;属性，而去修改 &lt;code&gt;&amp;lt;svg&amp;gt;.__zoom&lt;/code&gt;值。&lt;/p&gt;
&lt;h4&gt;放缩平移写法&lt;/h4&gt;
&lt;p&gt;在一开始时，使用d3.zoom()创建放缩对象 &lt;code&gt;zoom&lt;/code&gt;，并在任何时刻都使用 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;来 &lt;code&gt;call(zoom)&lt;/code&gt;修改放缩值。在绑定 &lt;code&gt;&quot;zoom&quot;&lt;/code&gt;事件时，因为 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; &lt;code&gt;call&lt;/code&gt;了 &lt;code&gt;zoom&lt;/code&gt;，因此任何偏移量都会记录在 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;，在修改 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;的 &lt;code&gt;&apos;transform&apos;&lt;/code&gt;属性时，可以直接使用 &lt;code&gt;d3.zoomTransform(svg.node())&lt;/code&gt;来获得 &lt;code&gt;&amp;lt;svg&amp;gt;.__zoom&lt;/code&gt;来进行应用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; svg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; g &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; svg.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; zoom &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;zoom&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;zoom&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	g.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, d3.&lt;/span&gt;&lt;span&gt;zoomTransform&lt;/span&gt;&lt;span&gt;(svg.&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;特殊修改 &lt;code&gt;&apos;transform&apos;&lt;/code&gt;函数的写法&lt;/h4&gt;
&lt;p&gt;这里需要说明一下 &lt;code&gt;autoZoom()&lt;/code&gt;的写法，假设我们现在已经计算出了 &lt;code&gt;&apos;transform&apos;&lt;/code&gt;数值 &lt;code&gt;transformX&lt;/code&gt;，&lt;code&gt;transformY&lt;/code&gt;，&lt;code&gt;k&lt;/code&gt;。现在需要修改 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;的 &lt;code&gt;__zoom&lt;/code&gt;属性为当前的 &lt;code&gt;&apos;transform&apos;&lt;/code&gt;数值。&lt;/p&gt;
&lt;p&gt;查阅了官方文档，找到了可以使用的API：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;d3.zoomIdentity&lt;/code&gt;。这个API可以创建一个新的 &lt;code&gt;&apos;transform&apos;:{x:0,y:0,k:1}&lt;/code&gt;，并允许使用 &lt;code&gt;transform.translate(x,y), transform.scale(k)&lt;/code&gt;对其进行更改。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;selection.call(zoom.transform,new_transform);&lt;/code&gt;使用这个接口能够将 &lt;code&gt;&amp;lt;svg&amp;gt;.__zoom&lt;/code&gt;修改为 &lt;code&gt;new_transform&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;综上，代码为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; new_transform &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.zoomIdentity.&lt;/span&gt;&lt;span&gt;translate&lt;/span&gt;&lt;span&gt;(transformX, transformY).&lt;/span&gt;&lt;span&gt;scale&lt;/span&gt;&lt;span&gt;(k);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;d3.&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(zoom.transform, new_transform);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;简而言之，任何对 &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;的放缩与平移操作，都需要作用在 &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt;上，并且使用 &lt;code&gt;&amp;lt;svg&amp;gt;.__zoom()&lt;/code&gt;来修改。&lt;/p&gt;
&lt;p&gt;完整代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//zoom事件绑定&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; svg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; g &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; svg.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; zoom &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;zoom&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;zoom&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	g.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, d3.&lt;/span&gt;&lt;span&gt;zoomTransform&lt;/span&gt;&lt;span&gt;(svg.&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//需要修改特定transform的函数，以autoZoom为例&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; autoZoom&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;transformX&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;transformY&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;k&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; new_transform &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.zoomIdentity.&lt;/span&gt;&lt;span&gt;translate&lt;/span&gt;&lt;span&gt;(transformX, transformY).&lt;/span&gt;&lt;span&gt;scale&lt;/span&gt;&lt;span&gt;(k);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	d3.&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(zoom.transform, new_transform);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>浅析Debounce与Throttle的区别</title><link>https://luckycc.cc/posts/debounce-vs-throttle/</link><guid isPermaLink="true">https://luckycc.cc/posts/debounce-vs-throttle/</guid><description>Debounce和Throttle是两个非常相似，但不相同的方法，它们都用来控制我们允许一个函数随时间执行的次数，用官方一点的说法就是：防止抖动，但是它们又有不同，具体见文</description><pubDate>Thu, 30 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这两天在学习前端知识，在Vue的官方教程中看到了这两个概念，查阅相关资料后，做以下整理。&lt;/p&gt;
&lt;h2&gt;概述&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Debounce&lt;/code&gt; 和 &lt;code&gt;Throttle&lt;/code&gt;是两个非常相似，但不相同的方法，它们都用来控制我们允许一个函数随时间执行的次数，用官方一点的说法就是：&lt;strong&gt;防止抖动&lt;/strong&gt;。但是它们又有不同，以鼠标点击事件绑定的函数 &lt;code&gt;clickFunc()&lt;/code&gt;为例：假设用户在10秒内不间断点击50下鼠标&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;debounce(clickFunc(),delay)&lt;/code&gt;是将这50次鼠标在用户停止点击鼠标点击动作的 &lt;code&gt;delay&lt;/code&gt;秒后，才响应一次。&lt;/li&gt;
&lt;li&gt;而 &lt;code&gt;throttle(clickFunc(),delay)&lt;/code&gt;是在 &lt;code&gt;delay&lt;/code&gt;秒的时间间隔内。无论用户点击多少次，只响应一次。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;讲到这里可能还不是很清楚，但没关系，接下来这个例子可以帮助你更好地理解它们的区别&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;再举一个例子：比如我们现在有一个函数需要响应用户的滚动交互。我们都知道，用户可以通过鼠标滚轮和拖动侧边的长条形bar进行滚动交互。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果滚动一个单位，我们就需要操作一次DOM元素。那么用户如果每秒滚动100个单位，是根本无法全部响应的，还会造成浏览器卡死。所以我们可以使用 &lt;code&gt;debounce()&lt;/code&gt;，让用户停止滚动后再响应。&lt;/li&gt;
&lt;li&gt;经常刷微博逛知乎的朋友应该都知道，我们即将滑动到底部的时候，前端会请求更多内容以方便我们查阅。这是怎么做到的呢？其实就是用了 &lt;code&gt;throttle(func()，delay)&lt;/code&gt;函数，其中 &lt;code&gt;func()&lt;/code&gt;是一个检测是否快到底部的函数，&lt;code&gt;delay&lt;/code&gt;用来控制多长时间间隔内，浏览器响应一次。用户执行滚动事件的时候，就会判定是否靠近底部，如果靠近底部就发一个AJAX请求新一批内容。这也避免了这个函数一直监听，用户靠近底部的每一次滑动都去请求服务器，然后给服务器带来很多不必要的负担。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;这两个函数在我们将某个函数绑定到操作DOM元素的事件上非常有用&lt;/strong&gt;，为什么这么说呢？
这两个函数的出现，其实相当于我们在事件响应和函数执行间添加了一个控制层。显然这是非常有必要的，&lt;strong&gt;因为我们无法控制用户如何进行交互，但是我们可以控制前端如何响应这些用户的交互。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Debounce&lt;/h3&gt;
&lt;p&gt;可以理解成把一些连续的操作视为一组序列，最终浏览器只把这组序列视为一个事件进行响应。
如图，第一排为用户的时间，第二排为浏览器的响应

“归为一组响应”的理解：

为了方便理解 &lt;code&gt;debounce()&lt;/code&gt;，在这里举个例子：想像现在有一个电梯，如果检测到当前有人进来就延迟5s关门。现在第一个人走进电梯，过了3s，还差2s关门的时候，电梯又走进来了一个人。那么在第二个人走进来之后，电梯会重设计时器，重新计时5s再关门。同理，第三个人如果在这5s间隔内走进电梯，电梯又会重新计时。&lt;/p&gt;
&lt;h4&gt;瞬时响应or延迟响应&lt;/h4&gt;
&lt;p&gt;其实这很容易带来一个问题，那就是现在我们介绍的 &lt;code&gt;debounce()&lt;/code&gt;函数事实上是延迟响应的。也就是说当我们进行连续的交互后，要过一定时间才会响应我们的交互事件。那如果我们要让它马上就响应一次我们的交互呢？&lt;code&gt;debounce()&lt;/code&gt;给我们提供了参数让我们来控制是瞬时响应还是延迟响应。
下面放上一张瞬时响应的示意图，可以看到，虽然还是把这些交互归为了一组，只响应一次。但&lt;strong&gt;响应的是用户一开始触发交互事件&lt;/strong&gt;：
&lt;/p&gt;
&lt;h4&gt;举例与注意事项&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;debounce()&lt;/code&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;code&gt;debounce()&lt;/code&gt;不能被多次调用，下面给出了一种正确用法与一种错误用法，请读者自行体会。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WRONG&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;(window).&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;scroll&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	_.&lt;/span&gt;&lt;span&gt;debounce&lt;/span&gt;&lt;span&gt;(doSomething, &lt;/span&gt;&lt;span&gt;300&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;RIGHT&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;(window).&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;scroll&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, _.&lt;/span&gt;&lt;span&gt;debounce&lt;/span&gt;&lt;span&gt;(doSomething, &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Throttle&lt;/h3&gt;
&lt;h4&gt;定义&lt;/h4&gt;
&lt;p&gt;这前面简单介绍过了，这里就不过多赘述。它的&lt;strong&gt;定义&lt;/strong&gt;是:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不允许某个函数在{delay}长度的时间间隔内执行超过一次&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;用法举例&lt;/h4&gt;
&lt;p&gt;它的用法与 &lt;code&gt;debounce()&lt;/code&gt;相似,以lodash.js为例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;_.&lt;/span&gt;&lt;span&gt;throttle&lt;/span&gt;&lt;span&gt;(dosomething, &lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主要用在检测用户界面是否已经靠近底部了，如果靠近底部就需要请求新的内容供用户查阅。&lt;/p&gt;
&lt;h3&gt;参考文章&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://css-tricks.com/debouncing-throttling-explained-examples/#top-of-site&quot;&gt;Debouncing and Throttling Explained Through Examples&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>【D3.js 学习记录（实战）】Force 力导图数据可视化</title><link>https://luckycc.cc/posts/force-simulation-d3/</link><guid isPermaLink="true">https://luckycc.cc/posts/force-simulation-d3/</guid><description>Force 力导图数据可视化（带拖拽）</description><pubDate>Sat, 07 Aug 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Force Simulation 力导图实战 @ D3.js&lt;/h2&gt;
&lt;h3&gt;数据结构&lt;/h3&gt;
&lt;p&gt;做力导图使用到的数据为json数据，一般采用json数据来表达图结构。本次实验选用的json数据数据结构如下（图片中为经过了力模拟后的）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;links至少要由 &lt;code&gt;target&lt;/code&gt;, &lt;code&gt;source&lt;/code&gt;组成
&lt;ul&gt;
&lt;li&gt;这连个属性标记了起始node和终止node，其值通
常为&lt;strong&gt;node的id。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;node由 &lt;code&gt;group&lt;/code&gt;和 &lt;code&gt;id&lt;/code&gt;构成
&lt;ul&gt;
&lt;li&gt;group表示node属于哪一类（方便着色）&lt;/li&gt;
&lt;li&gt;id是node的唯一标识符&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;思路分析&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;li&gt;数据绑定 datajoin
&lt;ul&gt;
&lt;li&gt;结点node（&lt;code&gt;&amp;lt;circle&amp;gt;&lt;/code&gt;和&lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;连边link&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;simulation.on(&quot;tick&quot;,tick)&lt;/code&gt;，tick函数编写&lt;/li&gt;
&lt;li&gt;drag 拖曳交互设计&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;画布初始化，全局变量定义&lt;/h3&gt;
&lt;p&gt;这步比较基础，直接贴上代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; svg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; height &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt;svg.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; width &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt;svg.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; margin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	top&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 150&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	left&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	right&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; innerHeight &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; height &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; margin.top &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; margin.bottom;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; innerWidth &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; width &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; margin.left &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; margin.right;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; color &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;scaleOrdinal&lt;/span&gt;&lt;span&gt;(d3.schemeCategory10);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; link;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; nodes;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; simulation;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; render_init&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	svg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;2em&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, margin.left)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, margin.top &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`translate(0,-40)`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;font-weight&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;bold&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;blue&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Force  Simulation&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	svg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;maingroup&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, margin.left)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, margin.top)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`translate(&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;margin.left&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;margin.top&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, innerWidth)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, innerHeight);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数据读取&lt;/h3&gt;
&lt;p&gt;通常读取进来的数据要进行预处理，但是图数据有些不同。&lt;strong&gt;通常我们拿到的数据都是不符合d3力导图数据结构要求的&lt;/strong&gt;。一般用python等语言写的&lt;strong&gt;脚本文件进行数据预处理&lt;/strong&gt;整理成符合要求的数据结构。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;d3.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./data/miserables.json&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	render_init&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;//画布初始化&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//force simulation 力模拟&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//data join 数据绑定&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//drag  交互事件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;力模拟&lt;/h3&gt;
&lt;p&gt;有两点需要注意的地方：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;forceLink这里要处理一下，绑定 &lt;code&gt;node.id&lt;/code&gt;。不然会按照node的索引来进行，这样设置tick的时候会非常不方便&lt;/li&gt;
&lt;li&gt;仅进行力模拟结点的位置不会进行实时更新，必须要有 &lt;code&gt;simulation.on(tick)&lt;/code&gt;才会把力模拟的结果反映到图元上&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;simulation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;forceSimulation&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;nodes&lt;/span&gt;&lt;span&gt;(data.nodes)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;force&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&apos;&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		d3.&lt;/span&gt;&lt;span&gt;forceLink&lt;/span&gt;&lt;span&gt;(data.links).&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.id),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;force&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;manyBody&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, d3.&lt;/span&gt;&lt;span&gt;forceManyBody&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;force&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;center&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, d3.&lt;/span&gt;&lt;span&gt;forceCenter&lt;/span&gt;&lt;span&gt;(innerWidth &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;, innerHeight &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, tick);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;数据绑定 datajoin&lt;/h3&gt;
&lt;p&gt;这里我们需要绑定的三个元素为： &lt;strong&gt;结点，结点名称，链接&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;结点与结点名称&lt;/h4&gt;
&lt;p&gt;一个传统的方法是 &lt;code&gt;circle&lt;/code&gt;和 &lt;code&gt;text&lt;/code&gt;分开绑定，但是这里采用一个更加高效的办法：就是创建一个&lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;, 每个g标签代表一个结点，其中包含 &lt;code&gt;circle&lt;/code&gt;和 &lt;code&gt;text&lt;/code&gt;两个图元&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建结点group &lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;nodes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; group&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;nodegroup&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//先创建一个group，其中包含所有结点（这步可有可无）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;selectAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;.node&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;(data.nodes)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;结点绑定(circle)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; circle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nodes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;circle&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; color&lt;/span&gt;&lt;span&gt;(d.group));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;结点名称绑定&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; label &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nodes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.id)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;12px&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;链接&lt;/h4&gt;
&lt;p&gt;这里有个坑，line标签是不能设置 &lt;code&gt;fill&lt;/code&gt;的，应该通过设置 &lt;code&gt;stroke&lt;/code&gt;属性来设置line的颜色。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//注意设置stroke&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;link &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; group&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;linkgroup&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;selectAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;(data.links)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;enter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;stroke-width&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;sqrt&lt;/span&gt;&lt;span&gt;(d.value))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;stroke&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;green&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;stroke-opacity&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.6&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;tick函数编写&lt;/h3&gt;
&lt;p&gt;在tick函数中，我们要更新&lt;strong&gt;node的位置&lt;/strong&gt;和&lt;strong&gt;link的起点和终点&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：node指包含了circle和text的那个group，这里只需要更新那个group的位置，&lt;code&gt;&amp;lt;circle&amp;gt;&lt;/code&gt;和&lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt;就会一起更新。这就是上面提出为什么要把&lt;code&gt;&amp;lt;circle&amp;gt;&lt;/code&gt;和&lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt;放在一个&lt;code&gt;&amp;lt;g&amp;gt;&lt;/code&gt;中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; tick&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	link&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;x1&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.source.x)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;y1&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.source.y)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;x2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.target.x)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;y2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.target.y);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	nodes.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; `translate(&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;d.x&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;d.y&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;drag 拖曳交互设计&lt;/h3&gt;
&lt;p&gt;现在给图加一些交互效果：让结点能够被鼠标选中并拖拽&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;d3.drag()&lt;/code&gt;函数&lt;/h4&gt;
&lt;p&gt;drag函数有三个需要配置的，分别是拖曳开始，拖曳过程和拖曳结束&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; dragFunc &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;drag&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, dragstarted).&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;drag&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, dragged).&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, dragended);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;dragstarted 拖曳开始&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;alphaTarget &lt;/code&gt;：衰减系数，对节点位置移动过程的模拟，数值越高移动越快，数值范围[0，1]&lt;/li&gt;
&lt;li&gt;&lt;code&gt;restart()&lt;/code&gt;： &lt;strong&gt;重新启动仿真的内部定时器并且返回仿真&lt;/strong&gt;。与 &lt;code&gt;simulation*.alphaTarget&lt;/code&gt;或 &lt;code&gt;simulation*.alpha&lt;/code&gt;结合使用，这个方法可以在交互期间再次激活仿真，比如拖拽节点或者在使用 &lt;code&gt;simulation.stop&lt;/code&gt;临时暂停仿真后使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; dragstarted&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;d3.event.active) simulation.&lt;/span&gt;&lt;span&gt;alphaTarget&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.3&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;restart&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	d.fx &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d.x;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	d.fy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d.y;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;dragged 拖曳过程&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;fx,fy&lt;/code&gt;：结点的最终位置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; dragged&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	d.fx &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.event.x;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	d.fy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.event.y;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;dragended 拖曳结束&lt;/h4&gt;
&lt;p&gt;最后将fx和fy设置成null，表示拖曳结束后让结点回到力模拟的位置，而不是停留在拖曳的位置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; dragended&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;d3.event.active) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		simulation.&lt;/span&gt;&lt;span&gt;alphaTarget&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//让它回到原来的位置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	d.fx &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	d.fy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;调用方式&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;dragFunc&lt;/span&gt;&lt;span&gt;(nodes); &lt;/span&gt;&lt;span&gt;//param：拖曳对象&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;完整代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&amp;lt;!&lt;/span&gt;&lt;span&gt;doctype&lt;/span&gt;&lt;span&gt; html&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&amp;gt;force&amp;lt;/&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt; src&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;./js/d3.min.js&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;/&lt;/span&gt;&lt;span&gt;head&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt; width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;1200&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;650&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; svg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; height &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt;svg.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; width &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt;svg.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; margin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				top&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 150&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				left&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				right&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 50&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				bottom&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; innerHeight &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; height &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; margin.top &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; margin.bottom;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; innerWidth &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; width &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; margin.left &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; margin.right;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			var&lt;/span&gt;&lt;span&gt; color &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;scaleOrdinal&lt;/span&gt;&lt;span&gt;(d3.schemeCategory10);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			let&lt;/span&gt;&lt;span&gt; link;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			let&lt;/span&gt;&lt;span&gt; nodes;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			let&lt;/span&gt;&lt;span&gt; simulation;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; render_init&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				svg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;2em&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, margin.left)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, margin.top &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`translate(0,-40)`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;font-weight&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;bold&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;blue&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;Force  Simulation&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				svg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;maingroup&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, margin.left)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, margin.top)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`translate(&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;margin.left&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;margin.top&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, innerWidth)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, innerHeight);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; tick&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; () {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				link&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;x1&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.source.x)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;y1&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.source.y)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;x2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.target.x)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;y2&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.target.y);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				nodes.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; `translate(&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;d.x&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;d.y&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; dragstarted&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;d3.event.active) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					//// 设置衰减系数，对节点位置移动过程的模拟，数值越高移动越快，数值范围[0，1]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					simulation.&lt;/span&gt;&lt;span&gt;alphaTarget&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.3&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;restart&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					d.fx &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d.x;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					d.fy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d.y;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; dragged&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				d.fx &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.event.x;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				d.fy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.event.y;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			const&lt;/span&gt;&lt;span&gt; dragended&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;d3.event.active) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					simulation.&lt;/span&gt;&lt;span&gt;alphaTarget&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				//让它回到原来的位置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				d.fx &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				d.fy &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			d3.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;./data/miserables.json&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;then&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(data);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				render_init&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				const&lt;/span&gt;&lt;span&gt; group &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3.&lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;#maingroup&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				simulation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;forceSimulation&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;nodes&lt;/span&gt;&lt;span&gt;(data.nodes)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;force&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						&apos;&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						d3.&lt;/span&gt;&lt;span&gt;forceLink&lt;/span&gt;&lt;span&gt;(data.links).&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.id),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;force&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;manyBody&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, d3.&lt;/span&gt;&lt;span&gt;forceManyBody&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;force&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;center&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, d3.&lt;/span&gt;&lt;span&gt;forceCenter&lt;/span&gt;&lt;span&gt;(innerWidth &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;, innerHeight &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, tick);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				//注意设置stroke&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				link &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; group&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;linkgroup&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;selectAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;(data.links)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;enter&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;stroke-width&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;sqrt&lt;/span&gt;&lt;span&gt;(d.value))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;stroke&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;green&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;stroke-opacity&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.6&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				nodes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; group&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;nodegroup&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;selectAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;.node&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;(data.nodes)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				var&lt;/span&gt;&lt;span&gt; circle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nodes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;circle&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; color&lt;/span&gt;&lt;span&gt;(d.group));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				var&lt;/span&gt;&lt;span&gt; label &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nodes&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; d.id)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;12px&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				const&lt;/span&gt;&lt;span&gt; dragFunc &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;drag&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, dragstarted)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;drag&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, dragged)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, dragended);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				dragFunc&lt;/span&gt;&lt;span&gt;(nodes); &lt;/span&gt;&lt;span&gt;//拖曳对象&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;script&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;/&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;效果&lt;/h2&gt;
&lt;h3&gt;初始&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;拖曳&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;</content:encoded></item><item><title>在博客中应用ipad-cursor，@Hexo(Theme:NexT)</title><link>https://luckycc.cc/posts/ipad-cursor-in-hexo/</link><guid isPermaLink="true">https://luckycc.cc/posts/ipad-cursor-in-hexo/</guid><description>前两天睡前刷`twitter`，偶然间刷到一个很棒的项目`ipad-cursor`，想把这个用在自己的博客中。文章记录了在博客中尝试使用ipad-cursor的实践全过程</description><pubDate>Sat, 29 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;起因&lt;/h2&gt;
&lt;p&gt;前两天睡前刷 &lt;code&gt;twitter&lt;/code&gt;，偶然间刷到一个很棒的项目 &lt;code&gt;ipad-cursor&lt;/code&gt;，想把这个用在自己的博客中。问了一下作者&lt;a href=&quot;https://github.com/CatsJuice&quot;&gt;@CatsJuice&lt;/a&gt;，发现暂未提供关于Hexo的支持，于是想着自己把这个功能加上，说不定还能写个 &lt;code&gt;hexo-plugin&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;效果&lt;/h2&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;使用方法&lt;/h2&gt;
&lt;h3&gt;方法1&lt;/h3&gt;
&lt;p&gt;只需要为想要添加 &lt;code&gt;ipad-cursor&lt;/code&gt;效果的元素添加属性 &lt;code&gt;data-cursor:block&lt;/code&gt;或 &lt;code&gt;data-cursor:text&lt;/code&gt;即可。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;创建 &lt;code&gt;ipad-cursor-hexo&lt;/code&gt;目录，放置在 &lt;code&gt;/themes/next/source/js&lt;/code&gt;下，&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建两个js文件：&lt;code&gt;config.js&lt;/code&gt;，&lt;code&gt;index.js&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;找到需要添加 &lt;code&gt;data-cursor&lt;/code&gt;的元素&lt;/p&gt;
&lt;p&gt;方式：直接在DevTools中选择，即可定位，如果元素没有 &lt;code&gt;class&lt;/code&gt;与 &lt;code&gt;id&lt;/code&gt;不方便定位，可以在 &lt;code&gt;hexo&lt;/code&gt;工程下找到这个元素对应的 &lt;code&gt;.swig&lt;/code&gt;文件，添加上 &lt;code&gt;id&lt;/code&gt;，方便选择器选择。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用js为其添加属性 &lt;code&gt;document.querySelector(&apos;xx&apos;)?.setAttribute(&apos;data-cursor&apos;,&apos;block&apos;)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建初始化函数 &lt;code&gt;init&lt;/code&gt;，监听 &lt;code&gt;DOMContentLoaded&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	document.&lt;/span&gt;&lt;span&gt;querySelectorAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (_.style.cursor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//document.querySelector(&apos;xxx&apos;).setAttribute(&apos;data-cursor&apos;,&apos;block&apos;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//document.querySelector(&apos;xxx&apos;).setAttribute(&apos;data-cursor&apos;,&apos;block&apos;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//......&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	cursor.&lt;/span&gt;&lt;span&gt;initCursor&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;document.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;DOMContentLoaded&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, init);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;注：如果 &lt;code&gt;selector&lt;/code&gt;难以选择，考虑直接修改 &lt;code&gt;.swig&lt;/code&gt;文件，在标签上直接添加。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;ipad-cursor&lt;/code&gt;具体用法请参考：&lt;a href=&quot;https://github.com/CatsJuice/ipad-cursor&quot;&gt;github/ipad-cursor&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;方法2: 使用ipad-cursor-hexo插件，【推荐 🔥🔥🔥】&lt;/h3&gt;
&lt;p&gt;使用&lt;a href=&quot;https://github.com/zqqcee/ipad-cursor-hexo&quot;&gt;ipad-cursor-hexo&lt;/a&gt;进行设置，ipad-cursor-hexo是一个使用&lt;strong&gt;配置项&lt;/strong&gt;进行ipad-cursor配置的库，只需要按照要求编写配置项，只需不超过10行代码，即可在你的博客中添加ipad-cursor&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建一个名为 &lt;code&gt;ipad-cursor&lt;/code&gt;的文件夹，放在目录 &lt;code&gt;${SourcePath}/themes/next/source/js&lt;/code&gt;下&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建一个js文件，名为 &lt;code&gt;index.js&lt;/code&gt;，在 &lt;code&gt;${SourcePath}/themes/next/source/js/ipad-cursor-hexo&lt;/code&gt;中&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;编写配置文件&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;首先，你可以配置需要在哪些标签上绑定样式，比如：&lt;/p&gt;
&lt;p&gt;如果你想要在 &lt;code&gt;&amp;lt;div id=&quot;article&quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; 上，添加 &lt;code&gt;data-cursor=&quot;text&quot;&lt;/code&gt;的属性，那么你应该编写如下配置项，对象的key是你想要在 &lt;code&gt;document.querySelectorAll&lt;/code&gt;传入的内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const config = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;div#article&quot;:{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    type:&apos;text&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想要配置它的 &lt;code&gt;cursor-style&lt;/code&gt;, 你可以添加如下配置项&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const config = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;div#article&quot;:{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    type:&apos;text&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    style:&apos;radius:50%&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你想要将所有的type为text的 &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt;内的所有 &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;修改为block，你应该使用如下配置语法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const config = {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;div#article&quot;:{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    type:&apos;text&apos;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    style:&apos;radius:50%&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    children:{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	&quot;a&quot;:{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;          type:&quot;block&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;其次，你可以配置cursor的样式，详见：&lt;a href=&quot;https://github.com/CatsJuice/ipad-cursor#config&quot;&gt;ipad-cursor config&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后，你可以配置一些副作用，比如让 &lt;code&gt;img&lt;/code&gt;标签不能被选中，因为被选中的 &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;会变暗&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; effect&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).style.userSelect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将 &lt;code&gt;index.js&lt;/code&gt;添加到hexo blog中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;打开 &lt;code&gt;${SourcePath}/themes/next/layout/_partials/head/head.swig&lt;/code&gt;文件&lt;/li&gt;
&lt;li&gt;在其中添加 &lt;code&gt;&amp;lt;script src=&quot;/js/ipad-cursor-hexo/index.js&quot; type=&quot;module&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;让 &lt;code&gt;document&lt;/code&gt;监听 &lt;code&gt;&quot;DOMContentLoaded&quot;&lt;/code&gt;事件，等DOM加载完毕后，执行 &lt;code&gt;init&lt;/code&gt;函数，&lt;code&gt;index.js&lt;/code&gt;的完整代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; init &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;https://unpkg.com/ipad-cursor-hexo@latest&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { config, cursorConfig } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;./config.js&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; effect&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	document.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;img&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).style.userSelect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;document.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;DOMContentLoaded&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; init&lt;/span&gt;&lt;span&gt;(config, cursorConfig, effect));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;详情请见：&lt;a href=&quot;https://github.com/zqqcee/ipad-cursor-hexo&quot;&gt;zqqcee/ipad-cursor-hexo&lt;/a&gt;，欢迎star🌟！&lt;/p&gt;
&lt;p&gt;欢迎访问我的个人博客，查看效果 &lt;a href=&quot;https://luckycc.cc&quot;&gt;zqqcee&lt;/a&gt;🔥&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;2024-10-19 更新：该博客已迁移至 &lt;a href=&quot;https://luckycc.cc&quot;&gt;luckycc.cc&lt;/a&gt;，这个效果没有继续沿用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;遇到的问题&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;鼠标样式不生效问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个问题主要呈现在当鼠标放置到 &lt;code&gt;&amp;lt;a&amp;gt;，&amp;lt;p&amp;gt;&lt;/code&gt;等标签中，鼠标会变为一个小手点击的样式。&lt;/li&gt;
&lt;li&gt;NexT主题中，在一些地方设置了cursor的属性，&lt;code&gt;ipad-cursor&lt;/code&gt;的原理是先设置 &lt;code&gt;cursor:none&lt;/code&gt;,再添加一个 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;随着鼠标移动。但是 &lt;code&gt;cursor:none&lt;/code&gt;会被NexT主题设置的样式覆盖掉。&lt;/li&gt;
&lt;li&gt;**解决方法：**需要手动将所有元素的style重新设置 &lt;code&gt;cursor:none&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;载入文章后cursor失效问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这个问题主要呈现在当载入文章时，&lt;code&gt;cursor&lt;/code&gt;失效。&lt;/li&gt;
&lt;li&gt;因为已经绑定了 &lt;code&gt;document.on(&apos;DOMContentLoaded&apos;)&lt;/code&gt;事件，排除ipad-cursor错误使用问题。进一步排查，发现在 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;中使用了相对路径引入js文件，而在载入文章后，相对路径改变，造成 &lt;code&gt;ipad-cursor-hexo.js&lt;/code&gt;文件无法生效问题&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解决方法&lt;/strong&gt;：在 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;使用绝对路径引入&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>JAVA的转型与动态绑定</title><link>https://luckycc.cc/posts/java-transtype-dynamicbind/</link><guid isPermaLink="true">https://luckycc.cc/posts/java-transtype-dynamicbind/</guid><description>浅析JAVA中的转型与动态绑定</description><pubDate>Tue, 20 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;上转型与下转型&lt;/h3&gt;
&lt;p&gt;想要理解java中的转型只需要记住一句话:
&lt;code&gt;&amp;lt;font color = &quot;red&quot; size=&quot;5&quot;&amp;gt;&lt;/code&gt;父类引用指向子类对象 &lt;code&gt;&amp;lt;/font&amp;gt;&lt;/code&gt;
这句话是什么意思呢？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Father&lt;/span&gt;&lt;span&gt; f1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Son&lt;/span&gt;&lt;span&gt;();   &lt;/span&gt;&lt;span&gt;// 这就叫 upcasting （向上转型)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 现在 f1 引用指向一个Son对象&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Son&lt;/span&gt;&lt;span&gt; s1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (Son)f1;   &lt;/span&gt;&lt;span&gt;// 这就叫 downcasting (向下转型)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 现在f1 还是指向 Son对象&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是以下代码就会出错：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;Father&lt;/span&gt;&lt;span&gt; f1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Father&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;Son&lt;/span&gt;&lt;span&gt; s1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (Son)f1;&lt;/span&gt;&lt;span&gt;//ERROR!!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为f1是父类的引用，指向了父类对象，父类对象不能够指向子类引用。&lt;/p&gt;
&lt;h3&gt;上转型的特点&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;上转型对象不能操作子类新增加的成员变量，&lt;strong&gt;不能使用子类新增的方法&lt;/strong&gt;。即为较子类B失去一些属性和功能，这些属性和功能是新增的。&lt;/li&gt;
&lt;li&gt;上转型对象可以操作子类继承或隐藏的成员变量，也&lt;strong&gt;可以使用子类继承的或重写的方法&lt;/strong&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;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;上转型的优点&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; static&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; dosleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Human&lt;/span&gt;&lt;span&gt; h) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	//这里的Human h是传入父类，如果传入子类可以将其上转型为父类&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    h.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里以父类为参数，调用有时用子类作为参数，就是利用了向上转型。这样使代码变得简洁。不然的话，如果 dosleep 以子类对象为参数，则有多少个子类就需要写多少个函数。这也体现了 JAVA 的抽象编程思想。&lt;/p&gt;
&lt;h3&gt;动态绑定&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;概念&lt;/strong&gt;
方法可以在沿着继承链的多个类中实现，子类可以重写父类的方法。JVM决定运行时调用哪个方法。这就是动态绑定。
&lt;strong&gt;原理&lt;/strong&gt;
假设，对象o是类C1的实例，其中C1是C2的子类，C2是C3的子类，那么o也是C2，C3的实例。如果对象o调用一个方法p，JVM会依次在类C1，C2，C3中查找方法p的实现**（首先看参数类型，找到最合适的参数类型。再在参数类型符合的类方法中按照C1,C2,C3的顺序来查找p）**，直到找到为止。&lt;/p&gt;
&lt;p&gt;这也是java中多态的一种体现形式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class 人｛&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  吃饭();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;｝&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;class 程序员 extends 人{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  吃饭();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  编码();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;另外有个函数:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;void doSomething(人){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   一个人.编码();//这里就会编译出错，因为编译器只知道参数类型是人，而人的通用行为里只有吃饭,没有编码&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;   一个人.吃饭();//这里没有编译错，因为编译器知道人有吃饭的行为。具体的行为是程序员吃饭还是工人吃饭，就看传的是什么人了，靠运行时函数的晚绑定实现。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>仿Jfinal 为了提供数据结构普适性而重写数据分页工具</title><link>https://luckycc.cc/posts/jfinal_pagination/</link><guid isPermaLink="true">https://luckycc.cc/posts/jfinal_pagination/</guid><description>由于项目中表设计有问题，导致一条sql语句很难将表连接起来，因此无法使用Jfianl自带的分页方法。为了提供数据结构普适性而重写数据分页工具，但该方法无法体现分页的本质，重写分页算法的目的是为了让分页算法能够适应各种数据类型。</description><pubDate>Fri, 23 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;提出目的&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;注&lt;/strong&gt;：这种方法无法体现分页的本质，重写分页算法的目的是为了让分页算法能够普适各种数据类型。即向分页算法中投入任何数据结构都可以进行分页&lt;/p&gt;
&lt;h3&gt;分页工具类代码&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;注：本文仅贴出代码，不介绍逻辑。 具体分页逻辑可以自行查看代码进行理解&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.PageMe.java &lt;strong&gt;(继承JFinal封装的Page类，是分类方法返回的数据类型。该类使用java泛型类机制，让分类算法普适于任何数据结构)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * &lt;/span&gt;&lt;span&gt;@author&lt;/span&gt;&lt;span&gt; cc&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; PageMe&lt;/span&gt;&lt;span&gt;&amp;lt;T&amp;gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; Page&lt;/span&gt;&lt;span&gt;&amp;lt;T&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    boolean&lt;/span&gt;&lt;span&gt; firstPage;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    @&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    public&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt; isFirstPage&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; firstPage;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    public&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; setFirstPage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt; firstPage&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.firstPage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; firstPage;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    @&lt;/span&gt;&lt;span&gt;Override&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    public&lt;/span&gt;&lt;span&gt; boolean&lt;/span&gt;&lt;span&gt; isLastPage&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; lastPage;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    public&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; setLastPage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt; lastPage&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        this&lt;/span&gt;&lt;span&gt;.lastPage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; lastPage;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    boolean&lt;/span&gt;&lt;span&gt; lastPage;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.PageKit.java &lt;strong&gt;（分页工具类，内有分页方法paginate() ）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; * &lt;/span&gt;&lt;span&gt;@author&lt;/span&gt;&lt;span&gt; cc&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;public&lt;/span&gt;&lt;span&gt; class&lt;/span&gt;&lt;span&gt; PageKit&lt;/span&gt;&lt;span&gt;&amp;lt;T&amp;gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;* &lt;/span&gt;&lt;span&gt;@return&lt;/span&gt;&lt;span&gt; PageMe&amp;lt;T&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;*/&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    public&lt;/span&gt;&lt;span&gt; PageMe&lt;/span&gt;&lt;span&gt;&amp;lt;T&amp;gt; &lt;/span&gt;&lt;span&gt;paginate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; pageNumber&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; pageSize&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;lt;T&amp;gt; &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        PageMe&lt;/span&gt;&lt;span&gt;&amp;lt;T&amp;gt; page &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; PageMe&lt;/span&gt;&lt;span&gt;&amp;lt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.&lt;/span&gt;&lt;span&gt;setTotalRow&lt;/span&gt;&lt;span&gt;(data.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.&lt;/span&gt;&lt;span&gt;setTotalPage&lt;/span&gt;&lt;span&gt;(pageSize &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; :&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;) Math.&lt;/span&gt;&lt;span&gt;ceil&lt;/span&gt;&lt;span&gt;((data.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; pageSize)));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        //基础数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (pageNumber &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            //如果pageNumber不符合规范，返回全部数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            page.&lt;/span&gt;&lt;span&gt;setList&lt;/span&gt;&lt;span&gt;(data);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt; page;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.&lt;/span&gt;&lt;span&gt;setFirstPage&lt;/span&gt;&lt;span&gt;(pageNumber &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.&lt;/span&gt;&lt;span&gt;setLastPage&lt;/span&gt;&lt;span&gt;(pageNumber &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; page.&lt;/span&gt;&lt;span&gt;getTotalPage&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.&lt;/span&gt;&lt;span&gt;setPageNumber&lt;/span&gt;&lt;span&gt;(pageNumber);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.&lt;/span&gt;&lt;span&gt;setPageSize&lt;/span&gt;&lt;span&gt;(pageSize);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        //根据前端传过来的数据配置&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (data.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            page.&lt;/span&gt;&lt;span&gt;setList&lt;/span&gt;&lt;span&gt;(Collections.&lt;/span&gt;&lt;span&gt;emptyList&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            return&lt;/span&gt;&lt;span&gt; page;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        int&lt;/span&gt;&lt;span&gt; beginIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (pageNumber &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; pageSize;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        int&lt;/span&gt;&lt;span&gt; endIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; pageNumber &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; pageSize;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        endIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(endIndex, data.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        beginIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(beginIndex, data.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        //case1: beginIndex在范围内，endIndex在范围外&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        List&lt;/span&gt;&lt;span&gt;&amp;lt;T&amp;gt; renderData;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        if&lt;/span&gt;&lt;span&gt; (beginIndex &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; endIndex &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            renderData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;subList&lt;/span&gt;&lt;span&gt;(beginIndex, endIndex);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            //case2: beginIndex在范围外&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            if&lt;/span&gt;&lt;span&gt; (beginIndex &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;()) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                renderData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                //case3：取中间数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                renderData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;subList&lt;/span&gt;&lt;span&gt;(beginIndex, endIndex);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        page.&lt;/span&gt;&lt;span&gt;setList&lt;/span&gt;&lt;span&gt;(renderData);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        return&lt;/span&gt;&lt;span&gt; page;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用方法&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;注：pageNumber和pageSize一般由前端给，后端负责接受这两个值。这里示例为了方便直接定义&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; pageNumber &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt; ; &lt;/span&gt;&lt;span&gt;//第一页&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; pageSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 8&lt;/span&gt;&lt;span&gt;	;&lt;/span&gt;&lt;span&gt;//一页八条数据&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;List&lt;/span&gt;&lt;span&gt;&amp;lt;Record&amp;gt; data; &lt;/span&gt;&lt;span&gt;// 一个装着元素类型为Record的List&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;PageKit&lt;/span&gt;&lt;span&gt;&amp;lt;Record&amp;gt; pageKit &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; PageKit&lt;/span&gt;&lt;span&gt;&amp;lt;&amp;gt;(); &lt;/span&gt;&lt;span&gt;// 每一条数据类型为Record&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;PageMe&lt;/span&gt;&lt;span&gt;&amp;lt;Record&amp;gt; data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; pageKit.&lt;/span&gt;&lt;span&gt;paginate&lt;/span&gt;&lt;span&gt;(pageNumber, pageSize, data);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后直接把data返回给前端即可&lt;/p&gt;
&lt;h3&gt;适用范围&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;由于表设计有问题，导致一条sql语句很难将表连接起来，因此无法使用Jfianl自带的分页方法时，可以考虑这种。 本文给出的分页思想是：**先将数据库中的数据查出来，并拼接成前端需要的数据类型**（如前端需要学生名字，学生班级，语文成绩，**但是他们在不同的表中又很难使用一条sql语句进行拼接**）。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，分页的目的本应是提高查找效率，本文提出的分页没有提高查找效率。&lt;/p&gt;</content:encoded></item><item><title>大型节点链接图快速渲染方案 @web worker的使用与改进</title><link>https://luckycc.cc/posts/large-graph-visulization/</link><guid isPermaLink="true">https://luckycc.cc/posts/large-graph-visulization/</guid><description>虽然Canvas可以用于渲染万级数据量，但是当节点数超过1w时，尽管一次渲染的时间很短，但还是会产生视觉上的卡顿。为此继续调研了一些优化方案，记录见文。</description><pubDate>Thu, 20 Jan 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;大型节点链接图快速渲染方案&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;虽然Canvas可以用于渲染万级数据量，但是当节点数超过1w时，尽管一次渲染的时间很短，但还是会产生视觉上的卡顿。为此，我们继续调研了一些优化方案，包括&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;基于Web Worker计算和渲染并行技术【实现并优化】&lt;/li&gt;
&lt;li&gt;基于Canvas的离屏渲染技术【实现】&lt;/li&gt;
&lt;li&gt;以及基于Canvas的3D框架—WebGL技术。【未尝试】&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1 基于Web Worker的计算与渲染的并行方法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;浏览器渲染页面是一个复杂的过程，因为浏览器内核是多线程的，整个过程需要涉及到多个线程，其中最重要的就是JS引擎线程和GUI渲染线程，其中JS引擎线程用来执行脚本文件，依照代码逻辑计算页面元素的位置；而GUI线程则将这些对应的页面元素渲染到页面上。为了防止在渲染过程中因元素的位置发生变化而导致渲染出错，**浏览器将GUI渲染线程与JS引擎设置为互斥的关系**，即当JS引擎执行时GUI线程会被挂起，GUI更新会被保存在一个队列中等到JS引擎空闲时被执行。在对一个图进行布局的时候，需要多次迭代以至于图中所有节点的位置达到稳定，那么就**需要JS引擎线程和GUI线程串行执行**，先利用JS引擎线程计算出下一次布局所有元素的位置，然后再利用GUI线程将所有元素渲染至页面上，以此类推，直到所有的迭代都完成。我们猜测这是导致大规模数据在渲染过程中产生卡顿的原因，因为计算节点下一次迭代的过程需要耗时，无法直接进行连续渲染。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;**Web Worker**是一种可为JavaScript创造多线程环境，并将一些高密度计算任务分配给子线程运行的方法,其具体工作流程如下图所示。我们尝试使用Web Worker将可视化工作流并行化来解决渲染卡顿的问题。我们将渲染工作与布局工作分开，具体操作如下：声明一个Worker子线程来执行高时间复杂度的布局迭代计算工作，并将每一次迭代后的计算结果返回给主线程。而主线程通过接收子线程的计算结果，进行每次迭代的布局渲染。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;由于布局渲染的方法有两种，分别为基于矢量的&lt;strong&gt;SVG&lt;/strong&gt;布局渲染方法以及基于位图的&lt;strong&gt;Canvas&lt;/strong&gt;布局渲染方法，我同时利用Web Worker进行计算与渲染的并行计算优化，实验Web Worker的有效性。&lt;/p&gt;
&lt;h4&gt;&lt;strong&gt;实验数据规模：&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;case1:&lt;/strong&gt; node(114),link(183)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;case2:&lt;/strong&gt; node(121),link(334)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;case3:&lt;/strong&gt; node(207),link(458)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;case4:&lt;/strong&gt; node(368),link(617)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;case5:&lt;/strong&gt; node(589),link(1057)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;case6:&lt;/strong&gt; node(1079),link(2345)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;case7:&lt;/strong&gt; node(301),link(480)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;case8:&lt;/strong&gt; node(385),link(144)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;case9:&lt;/strong&gt; node(429),link(910)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;case10:&lt;/strong&gt; node(2345),link(5217)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;case11:&lt;/strong&gt; node(1589),link(5217)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.1 基于Web Worker计算与渲染并行的SVG布局渲染方法&lt;/h3&gt;
&lt;h4&gt;实验结果：&lt;/h4&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;strong&gt;Case1&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case2&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case3&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case4&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case5&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case6&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case7&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;节点数&lt;/td&gt;&lt;td&gt;114&lt;/td&gt;&lt;td&gt;121&lt;/td&gt;&lt;td&gt;207&lt;/td&gt;&lt;td&gt;384&lt;/td&gt;&lt;td&gt;589&lt;/td&gt;&lt;td&gt;1079&lt;/td&gt;&lt;td&gt;301&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;未使用Web Worker&lt;/td&gt;&lt;td&gt;1877&lt;/td&gt;&lt;td&gt;1705&lt;/td&gt;&lt;td&gt;2001&lt;/td&gt;&lt;td&gt;2757&lt;/td&gt;&lt;td&gt;4934&lt;/td&gt;&lt;td&gt;7336&lt;/td&gt;&lt;td&gt;2247&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;使用Web Worker&lt;/td&gt;&lt;td&gt;1001&lt;/td&gt;&lt;td&gt;2544&lt;/td&gt;&lt;td&gt;2460&lt;/td&gt;&lt;td&gt;3037&lt;/td&gt;&lt;td&gt;5460&lt;/td&gt;&lt;td&gt;11509&lt;/td&gt;&lt;td&gt;3830&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;速度提升(ms)&lt;/td&gt;&lt;td&gt;87.51%&lt;/td&gt;&lt;td&gt;-32.98%&lt;/td&gt;&lt;td&gt;-18.66%&lt;/td&gt;&lt;td&gt;-9.22%&lt;/td&gt;&lt;td&gt;-9.63%&lt;/td&gt;&lt;td&gt;-36.26%&lt;/td&gt;&lt;td&gt;-41.33%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;案例&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case8&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case9&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case10&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case11&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;3-1(3k)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;1-1(6k)&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;6-1(1w)&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;节点数&lt;/td&gt;&lt;td&gt;114&lt;/td&gt;&lt;td&gt;429&lt;/td&gt;&lt;td&gt;2345&lt;/td&gt;&lt;td&gt;1589&lt;/td&gt;&lt;td&gt;3228&lt;/td&gt;&lt;td&gt;7987&lt;/td&gt;&lt;td&gt;18460&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;未使用Web Worker&lt;/td&gt;&lt;td&gt;1869&lt;/td&gt;&lt;td&gt;4148&lt;/td&gt;&lt;td&gt;16431&lt;/td&gt;&lt;td&gt;11954&lt;/td&gt;&lt;td&gt;23276&lt;/td&gt;&lt;td&gt;50808&lt;/td&gt;&lt;td&gt;201359&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;使用Web Worker&lt;/td&gt;&lt;td&gt;2753&lt;/td&gt;&lt;td&gt;5344&lt;/td&gt;&lt;td&gt;29163&lt;/td&gt;&lt;td&gt;20176&lt;/td&gt;&lt;td&gt;29797&lt;/td&gt;&lt;td&gt;98663&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;速度提升(ms)&lt;/td&gt;&lt;td&gt;-32.11%&lt;/td&gt;&lt;td&gt;-22.38%&lt;/td&gt;&lt;td&gt;-43.66%&lt;/td&gt;&lt;td&gt;-40.75%&lt;/td&gt;&lt;td&gt;-21.88%&lt;/td&gt;&lt;td&gt;-48.50%&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;在基于矢量的SVG布局渲染方法的基础上，经过Web Worker计算与渲染并行的优化实验可知，只有在案例1中使用Web Worker的耗时比不使用Web Worker有所提升，但在其他的案例中，通过将计算和渲染分为两个线程反而会造成耗时成本增加。我们分析导致这个问题的原因是：**在计算和渲染中，一次渲染的时间远远大于一次布局迭代计算的时间**。这样就会出现子线程的布局结果早已计算完毕，但主线程的渲染工作还未完成的情况，消息队列会因此堆积大量子线程发送的布局结果 ，而每次都需要从消息队列中取出数据存在的耗时比在主线程上完成布局迭代计算的时间成本还高。因此，经过实验可知，**如果选择SVG作为渲染方法，使用Web Worker无法给用户带来良好的视觉体验**。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;完整代码见&lt;a href=&quot;https://github.com/zqqcee/large_scale_Vis/blob/main/src/svgWorker.html&quot;&gt;github -&amp;gt; svgWorker&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.2 基于Web Worker计算与渲染并行的Canvas布局渲染方法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;当选择使用Canvas作为渲染方法的时候，由于Canvas单次渲染的时间很短，不会出现消息队列大量堆积子线程发送的布局结果。我们猜想，在这种情况下，使用Web Worker对渲染性能的提升是有效的。因此，我们对Canvas开展了计算与渲染并行对实验。具体的实验（**数据规模同上**）如下表所示：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;strong&gt;Case1&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case2&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case3&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case4&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case5&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case6&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case7&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;节点数&lt;/td&gt;&lt;td&gt;114&lt;/td&gt;&lt;td&gt;121&lt;/td&gt;&lt;td&gt;207&lt;/td&gt;&lt;td&gt;384&lt;/td&gt;&lt;td&gt;589&lt;/td&gt;&lt;td&gt;1079&lt;/td&gt;&lt;td&gt;301&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;未使用Web Worker&lt;/td&gt;&lt;td&gt;56&lt;/td&gt;&lt;td&gt;76&lt;/td&gt;&lt;td&gt;103&lt;/td&gt;&lt;td&gt;169&lt;/td&gt;&lt;td&gt;267&lt;/td&gt;&lt;td&gt;516&lt;/td&gt;&lt;td&gt;160&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;使用Web Worker&lt;/td&gt;&lt;td&gt;121&lt;/td&gt;&lt;td&gt;122&lt;/td&gt;&lt;td&gt;143&lt;/td&gt;&lt;td&gt;256&lt;/td&gt;&lt;td&gt;452&lt;/td&gt;&lt;td&gt;576&lt;/td&gt;&lt;td&gt;213&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;速度提升(ms)&lt;/td&gt;&lt;td&gt;-53.72%&lt;/td&gt;&lt;td&gt;-37.70%&lt;/td&gt;&lt;td&gt;-27.97%&lt;/td&gt;&lt;td&gt;-33.98%&lt;/td&gt;&lt;td&gt;-40.9%&lt;/td&gt;&lt;td&gt;-10.4%&lt;/td&gt;&lt;td&gt;-24.88%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;案例&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case8&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case9&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case10&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case11&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;3-1&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;1-1&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;6-1&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;节点数&lt;/td&gt;&lt;td&gt;114&lt;/td&gt;&lt;td&gt;429&lt;/td&gt;&lt;td&gt;2345&lt;/td&gt;&lt;td&gt;1589&lt;/td&gt;&lt;td&gt;3228&lt;/td&gt;&lt;td&gt;7987&lt;/td&gt;&lt;td&gt;18460&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;未使用Web Worker&lt;/td&gt;&lt;td&gt;88&lt;/td&gt;&lt;td&gt;197&lt;/td&gt;&lt;td&gt;1333&lt;/td&gt;&lt;td&gt;1346&lt;/td&gt;&lt;td&gt;1739&lt;/td&gt;&lt;td&gt;5120&lt;/td&gt;&lt;td&gt;12337&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;使用Web Worker&lt;/td&gt;&lt;td&gt;183&lt;/td&gt;&lt;td&gt;244&lt;/td&gt;&lt;td&gt;1908&lt;/td&gt;&lt;td&gt;1390&lt;/td&gt;&lt;td&gt;1755&lt;/td&gt;&lt;td&gt;5072&lt;/td&gt;&lt;td&gt;13837&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;速度提升(ms)&lt;/td&gt;&lt;td&gt;-51.91%&lt;/td&gt;&lt;td&gt;-19.26%&lt;/td&gt;&lt;td&gt;-30.14%&lt;/td&gt;&lt;td&gt;-3.17%&lt;/td&gt;&lt;td&gt;-0.91%&lt;/td&gt;&lt;td&gt;0.95%&lt;/td&gt;&lt;td&gt;-10.84%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;对比起svg渲染，使用canvas可以明显地提升渲染性能。主要原因是canvas的单词渲染耗时大幅缩短。但是经过多次实验对比，发现**在节点数量在超过3k时，会出现明显的卡顿，渲染效果仍并不理想**。更糟糕的是，比起不使用Web Worker，使用Web Worker时Canvas的渲染表现反而更差劲了，用户界面仍然存在非常明显的卡顿。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;完整代码见 &lt;a href=&quot;https://github.com/zqqcee/large_scale_Vis/blob/main/src/d3-canvas-worker.html&quot;&gt;github -&amp;gt; canvas worker&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;2 优化I/O损耗&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;在实验过程中我们发现，尽管使用Canvas可以缩短单次渲染的时间，不会出现消息队列大量堆积Worker线程发送的布局结果，但用户界面渲染仍出现较明显的卡顿。经过**性能分析**，我们发现**主要是主线程中的一个数据接收函数占用了大量时间**，这个函数在主线程中的作用是接收Worker线程发送过来的数据。当节点数量超过1w时，主线程与Worker线程的数据交换会占用大量时间。因此，需要尽可能缩短主线程与Worker线程的数据交换的时间以达到流畅渲染的目的。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;而Worker线程与主线程之间进行数据传递的方式有两种：一种是通过**对象拷贝**的方式，另一种是通过**转移对象引用的所有权**的方式。使用对象拷贝的方式，通过内部的克隆算法，将主线程的数据拷贝一份，传给worker。这样worker改变数据并不会影响到主线程。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;另一种通过转移的方式（Transferrable Objects），**不做任何拷贝，而是直接将数据值的引用所有权转移给 worker。**如果一个对象的引用所有权被转移，主线程不会再持有该对象的引用，那么该对象在它被发送的上下文中将变得不可用，并且只对它被转移到的Worker线程可用。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;我们实验了这两种数据传递方式的性能指标，发现在单次数据传递的耗时上，使用转移的方式明显优于对象拷贝。下图是在1w节点数据集上，两种数据传递方式的实验结果。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;可以看出性能面板上，使用对象拷贝方式一次数据传递的任务耗时107.0ms，而使用传递的方式任务耗时仅有21.2ms。**使用转移的方式进行数据传递，要求传递的对象必须为如ArrayBuffer等的指定格式**，因此这**牺牲了数据的可读性**，但能大幅提升数据I/O性能。对此，我们进行了数据I/O的性能实验。我们测试了主线程与Worker线程单次I/O的时间损耗，实验结果如下表所示：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;strong&gt;Case1&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case2&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case3&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case4&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case5&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case6&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;&lt;strong&gt;Case7&lt;/strong&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;节点数&lt;/td&gt;&lt;td&gt;114&lt;/td&gt;&lt;td&gt;121&lt;/td&gt;&lt;td&gt;207&lt;/td&gt;&lt;td&gt;384&lt;/td&gt;&lt;td&gt;589&lt;/td&gt;&lt;td&gt;1079&lt;/td&gt;&lt;td&gt;301&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;未使用Web Worker&lt;/td&gt;&lt;td&gt;56&lt;/td&gt;&lt;td&gt;76&lt;/td&gt;&lt;td&gt;103&lt;/td&gt;&lt;td&gt;169&lt;/td&gt;&lt;td&gt;267&lt;/td&gt;&lt;td&gt;516&lt;/td&gt;&lt;td&gt;160&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;使用对象拷贝&lt;/td&gt;&lt;td&gt;28&lt;/td&gt;&lt;td&gt;18&lt;/td&gt;&lt;td&gt;22&lt;/td&gt;&lt;td&gt;24&lt;/td&gt;&lt;td&gt;32&lt;/td&gt;&lt;td&gt;32&lt;/td&gt;&lt;td&gt;20&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;使用Transfer Object&lt;/td&gt;&lt;td&gt;34&lt;/td&gt;&lt;td&gt;26&lt;/td&gt;&lt;td&gt;30&lt;/td&gt;&lt;td&gt;30&lt;/td&gt;&lt;td&gt;43&lt;/td&gt;&lt;td&gt;74&lt;/td&gt;&lt;td&gt;35&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;速度提升(ms)&lt;/td&gt;&lt;td&gt;17.65%&lt;/td&gt;&lt;td&gt;30.77%&lt;/td&gt;&lt;td&gt;26.67%&lt;/td&gt;&lt;td&gt;20.00%&lt;/td&gt;&lt;td&gt;25.58%&lt;/td&gt;&lt;td&gt;56.76%&lt;/td&gt;&lt;td&gt;42.86%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;案例&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case8&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case9&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case10&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Case11&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;3-1&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;1-1&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;6-1&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;节点数&lt;/td&gt;&lt;td&gt;114&lt;/td&gt;&lt;td&gt;429&lt;/td&gt;&lt;td&gt;2345&lt;/td&gt;&lt;td&gt;1589&lt;/td&gt;&lt;td&gt;3228&lt;/td&gt;&lt;td&gt;7987&lt;/td&gt;&lt;td&gt;18460&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;未使用Web Worker&lt;/td&gt;&lt;td&gt;88&lt;/td&gt;&lt;td&gt;197&lt;/td&gt;&lt;td&gt;1333&lt;/td&gt;&lt;td&gt;1346&lt;/td&gt;&lt;td&gt;1739&lt;/td&gt;&lt;td&gt;5120&lt;/td&gt;&lt;td&gt;12337&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;使用对象拷贝&lt;/td&gt;&lt;td&gt;21&lt;/td&gt;&lt;td&gt;27&lt;/td&gt;&lt;td&gt;41&lt;/td&gt;&lt;td&gt;47&lt;/td&gt;&lt;td&gt;66&lt;/td&gt;&lt;td&gt;145&lt;/td&gt;&lt;td&gt;452&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;使用Transfer Object&lt;/td&gt;&lt;td&gt;28&lt;/td&gt;&lt;td&gt;35&lt;/td&gt;&lt;td&gt;117&lt;/td&gt;&lt;td&gt;126&lt;/td&gt;&lt;td&gt;151&lt;/td&gt;&lt;td&gt;483&lt;/td&gt;&lt;td&gt;1151&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;速度提升(ms)&lt;/td&gt;&lt;td&gt;25.00%&lt;/td&gt;&lt;td&gt;22.86%&lt;/td&gt;&lt;td&gt;64.96%&lt;/td&gt;&lt;td&gt;62.70%&lt;/td&gt;&lt;td&gt;56.29%&lt;/td&gt;&lt;td&gt;69.98%&lt;/td&gt;&lt;td&gt;60.73%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;对实验结果进行分析，由表可以看出，随着节点数的增加，使用Transferrable Objects的数据传输方式，对数据I/O性能的提升效果显著。当节点数量超过2k时，数据I/O的速度平均能够提升65%。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.1 将json转为ArrayBuffer处代码实现&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;**只需要将links数据处理并传到worker线程中**&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//DEFINE IN MAIN&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; nodeInfoMap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	e &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	linkInfoMap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//创建node地图&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data.nodes.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;n&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	nodeInfoMap[n.id] &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		((nodeInfoMap[n.id] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			index&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; e,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; n.id,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		e&lt;/span&gt;&lt;span&gt;++&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//创建i，linkbuffer的原型&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;data.links.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	let&lt;/span&gt;&lt;span&gt; r &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &apos;&apos;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;concat&lt;/span&gt;&lt;span&gt;(e.source, &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;concat&lt;/span&gt;&lt;span&gt;(e.target); &lt;/span&gt;&lt;span&gt;//每条边对应的唯一id&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	linkInfoMap[r] &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		(i.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(nodeInfoMap[e.source].index, nodeInfoMap[e.target].index),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		(linkInfoMap[r] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; r,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		}));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//得到linkbuffer&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; linkBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; new&lt;/span&gt;&lt;span&gt; Int32Array&lt;/span&gt;&lt;span&gt;(i);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;完整代码见 &lt;a href=&quot;https://github.com/zqqcee/large_scale_Vis/blob/main/src/d3-canvas-worker-transfer.html&quot;&gt;github -&amp;gt; worker-transfer&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;1.1.3 基于Web Worker+离屏渲染的优化方法—zqc实验【已完成】&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;在正常的渲染过程中，CPU会将计算好的内容提交到GPU，GPU渲染完成后将渲染结果放入缓冲区，随后显示器会显示缓冲区中的数据。其中GPU屏幕渲染有以下两种方式：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（1）当前屏幕渲染（On-Screen Rendering）：指的是GPU的渲染操作作用于当前所显示的屏幕缓冲区；&lt;/p&gt;
&lt;p&gt;（2）离屏渲染（Off-Screen Rendering）：指的是GPU在当前屏幕缓冲区之外，新开辟一个缓冲区进行渲染操作。渲染的结果不会直接呈现到当前屏幕上，而是等待合适的时机才会显示。相当于在某个时间直接将已经渲染好的图片显示在屏幕上，则不必再执行所有绘图指令。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;实现离屏渲染的基本思路，是要将需要重复渲染的图形缓存为图片，在渲染时将图片直接从缓存中读取至另外的画布上。这样做的目的是希望减少在主画布中原生Canvas渲染接口的调用次数，以提升渲染效率。由于在大图可视化任务中，我们需要大量重复地绘制圆点，因此我们尝试将它们缓存为图片，在渲染时直接读取至除主画布外的画布上即可。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;由于浏览器的离屏渲染技术是基于Canvas的，所以在这个部分，我们只对Canvas采取离屏渲染优化。大图可视化任务可分为布局与渲染两个子任务，离屏渲染技术只能提升渲染这一子任务的性能，而对布局这一子任务的性能没有任何影响。因此，将离屏渲染应用于大图可视化中具有一项前提条件：计算布局的Worker线程速度快于主线程渲染的速度。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;如下图实验结果所示，使用canvas渲染大图的过程中，计算布局的Worker线程的时间开销远远高于主线程渲染。因此在基于Canvas的大图可视化任务中，使用离屏渲染并不会达到提升性能的目的。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;完整代码见&lt;a href=&quot;https://github.com/zqqcee/large_scale_Vis/blob/main/src/d3-canvas-worker-offscreen.html&quot;&gt;github -&amp;gt; offscreen&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>【解题笔记】 leetcode寻找两个正序数组的中位数</title><link>https://luckycc.cc/posts/leetcode-find-median/</link><guid isPermaLink="true">https://luckycc.cc/posts/leetcode-find-median/</guid><description>leetcode【寻找两个正序数组中位数】做题笔记，包含解题步骤、思考过程与完整代码</description><pubDate>Tue, 11 Oct 2022 00: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;/ul&gt;
&lt;p&gt;下面考虑两个有序数组，我们可以在两个数组上都划分一条分割线，这两条分割线有以下两个条件 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两条分割线左边的元素个数 = 两条分割线右边的元素个数&lt;/li&gt;
&lt;li&gt;两条分割线左边的元素均小于右边的元素
此时，这道题就从寻找中位数转化为了寻找满足上述两个条件的分割线。题目要求时间复杂度为 &lt;code&gt;O(log (m+n))&lt;/code&gt;，因此能够直接联想到使用二分查找法来找分割线。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解题步骤&lt;/h2&gt;
&lt;p&gt;寻找满足上述两个条件的分割线，那么我们就&lt;strong&gt;围绕上述两个条件&lt;/strong&gt;来编码：为了描述方便，将nums1设置为长度较短的数组，nums2设置为长度较长的数组。&lt;/p&gt;
&lt;h3&gt;第一个条件：&lt;/h3&gt;
&lt;p&gt;要考虑奇数和偶数的情况，如果两个数组长度之和为奇数，那么我们就规定左边元素比右边元素多；如果两个数组长度之和为偶数，那么两边元素相等。由于Java的除法是向下取整（即5/2=2），因此可以讲奇偶两种情况合并，得到左边元素的总个数&lt;/p&gt;
&lt;p&gt;$$
totalLeft = \frac{m+n+1}{2}~~~~~~~~//其中m，n分别代表两个数组的长度
$$&lt;/p&gt;
&lt;h3&gt;第二个条件：&lt;/h3&gt;
&lt;p&gt;要使分割线左边元素均小于右边的元素，因为两个数组均为有序数组，那么满足以下条件即可：
设 &lt;code&gt;i&lt;/code&gt;为nums1分割线右边的元素，&lt;code&gt;j&lt;/code&gt;为nums2分割线右边的元素。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;nums1[i&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt;nums2[j] &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; nums2[j&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt;nums1[i]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;根据上述两个条件编码：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;据此，根据第一个条件，我们可以知道 &lt;code&gt;i&lt;/code&gt;和 &lt;code&gt;j&lt;/code&gt;的等量关系，即 &lt;code&gt;i+j=totalLeft&lt;/code&gt;。知道这个等量关系以后就很好办了，我们每次只需要移动 &lt;code&gt;i&lt;/code&gt;，让 &lt;code&gt;j=totalLeft-i&lt;/code&gt;即可。&lt;/li&gt;
&lt;li&gt;根据第二个条件，我们只需要比较 &lt;code&gt;nums1[i-1]&lt;/code&gt;与 &lt;code&gt;nums2[j]&lt;/code&gt;的大小关系即可。如果前者大，说明分割线太靠右了；反之，继续向右寻找看还有没有满足条件的。
如下图：此时 &lt;code&gt;i&lt;/code&gt;指向元素2，&lt;code&gt;j&lt;/code&gt;指向元素4。&lt;code&gt;1&amp;lt;4&lt;/code&gt;，因此 &lt;code&gt;nums1&lt;/code&gt;的分割线右移。由 &lt;code&gt;j=totalLeft-i&lt;/code&gt;得，&lt;code&gt;nums2&lt;/code&gt;的分割线左移。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
用&lt;strong&gt;二分查找法&lt;/strong&gt;编码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//第一步：将长度最短的数组设置为nums1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (nums2.length &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; nums1.length) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  int&lt;/span&gt;&lt;span&gt;[] temp &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nums1;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  nums1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nums2;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  nums2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; temp;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//第二步：设置分割线左边元素的个数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; m &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nums1.length;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nums2.length;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; totalLeft &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (m &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;//合并奇数和偶数的情况&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//第三步，设置left与right，代表nums1分割线的查找区间。注：right需要设置为nums1.length，因为i可以为nums1.length,此时分割线就在nums1的最右边&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; left &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; right &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nums1.length;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/*两个条件：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        1. 分隔线左边的元素个数等于分隔线右边的元素个素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        2. 分隔线左边的所有元素均小于分隔线右边的元素个素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        即nums1[i-1] &amp;lt;= nums2[j] &amp;amp;&amp;amp; nums2[j-1] &amp;lt;= num1[i]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        注：i是nums1分割线右边的第一个元素，它的下标 = 分隔线左边元素的个数；&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        j同理，因此: i + j = totalLeft，可以根据该表达式，由i确定j。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; (left &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; right) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  int&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; left &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; (right &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; left &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  int&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; totalLeft &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; i;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; (nums1[i &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; nums2[j]) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //说明nums1的分隔线太靠右了，需要在[left,i-1处继续寻找]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    right &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //需要在[i,right]处继续寻找&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    left &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; i;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//第四步：分割线划分完毕，确定两个数组分割线右边的位置i，j。此时left所指向的元素是nums1分割线右边的元素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; left;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; totalLeft &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; i;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;极端情况：&lt;/h3&gt;
&lt;p&gt;下面讨论四种分割线划分的极端情况，仅以两种情况举例说明&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nums1&lt;/code&gt;的分割线在数组最左边
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因为 &lt;code&gt;nums1&lt;/code&gt;分隔线左边没有元素，因此可以得出两个数组分割线左边的最大值肯定在 &lt;code&gt;nums2&lt;/code&gt;中。&lt;/li&gt;
&lt;li&gt;此时要把 &lt;code&gt;nums1[i-1]&lt;/code&gt;设置为无限小的值，使得最后选择左边元素最大值的时候不要选中它。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nums1&lt;/code&gt;的分割线在数组最右边
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;因为 &lt;code&gt;nums1&lt;/code&gt;分隔线右边没有元素，因此可以得出两个数组分割线右边的最小值肯定在 &lt;code&gt;nums2&lt;/code&gt;中。&lt;/li&gt;
&lt;li&gt;此时要把 &lt;code&gt;nums1[i]&lt;/code&gt;设置为无限大的值，使得最后选择右边元素最小值的时候不要选中它。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nums2&lt;/code&gt;的分割线在数组最左边&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;nums2&lt;/code&gt;的分割线在数组最左边
因此，考虑到四种极端情况，要在获得中位数前加上以下代码：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; nums1LeftMax &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; Integer.MIN_VALUE &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; nums1[i &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;//此时nums1分割线左边没有元素了，因此nums1分割线左边元素的最大值要设置成无限小，在比较时直接选中nums2分割线的左边元素，其余同理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; nums1RightMin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; m &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; Integer.MAX_VALUE &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; nums1[i];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; nums2LeftMax &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; Integer.MIN_VALUE &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; nums2[j &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt; nums2RightMin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; Integer.MAX_VALUE &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; nums2[j];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//最后一步：中位数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; ((m &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; n) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; ==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  //偶数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt;)(Math.&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(nums1LeftMax, nums2LeftMax) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(nums1RightMin, nums2RightMin)) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(nums1LeftMax, nums2LeftMax);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;注意&lt;/h2&gt;
&lt;p&gt;在写二分查找法时，如果&lt;strong&gt;查找到右区间时&lt;/strong&gt;使用 &lt;code&gt;left=middle&lt;/code&gt;的方式编码，那么需要注意避免&lt;strong&gt;死循环&lt;/strong&gt;的情况。
比如某个区间只有两个数 &lt;code&gt;[i,j]&lt;/code&gt;，如果 &lt;code&gt;left=i&lt;/code&gt;，&lt;code&gt;right=j&lt;/code&gt;，那么若中间值一直不动，就会陷入死循环。因此确定中间值的时候应该使用 &lt;code&gt;left + (right - left + 1) / 2&lt;/code&gt;,这样就能保证如果二分查找查到了右区间，左边界加一。详情可见代码&lt;/p&gt;
&lt;h2&gt;完整代码&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; Solution&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  public&lt;/span&gt;&lt;span&gt; double&lt;/span&gt;&lt;span&gt; findMedianSortedArrays&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;nums1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;[] &lt;/span&gt;&lt;span&gt;nums2&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //第一步：将长度最短的数组设置为nums1&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; (nums2.length &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; nums1.length) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      int&lt;/span&gt;&lt;span&gt;[] temp &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nums1;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      nums1 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nums2;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      nums2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; temp;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //第二步：设置分割线左边元素的个数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; m &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nums1.length;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nums2.length;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; totalLeft &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (m &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;//合并奇数和偶数的情况&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //第三步，设置left与right，代表nums1分割线的查找区间。注：right需要设置为nums1.length，因为i可以为nums1.length,此时分割线就在nums1的最右边&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; left &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; right &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nums1.length;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    /*两个条件：&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        1. 分隔线左边的元素个数等于分隔线右边的元素个素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        2. 分隔线左边的所有元素均小于分隔线右边的元素个素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        即nums1[i-1] &amp;lt;= nums2[j] &amp;amp;&amp;amp; nums2[j-1] &amp;lt;= num1[i]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        注：i是nums1分割线右边的第一个元素，它的下标 = 分隔线左边元素的个数；&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        j同理，因此: i + j = totalLeft，可以根据该表达式，由i确定j。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    while&lt;/span&gt;&lt;span&gt; (left &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; right) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      int&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; left &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; (right &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; left &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      int&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; totalLeft &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; i;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      if&lt;/span&gt;&lt;span&gt; (nums1[i &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; nums2[j]) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        //说明nums1的分隔线太靠右了，需要在[left,i-1处继续寻找]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        right &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        //需要在[i,right]处继续寻找&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        left &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; i;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //第四步：分割线划分完毕，确定两个数组分割线右边的位置i，j。此时left所指向的元素是nums1分割线右边的元素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; left;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; totalLeft &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; i;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //第五步：确定中位数，无论是奇数还是偶数，中位数都只与两个数组分割线左边元素的最大值x 和 右边元素的最小值y 有关。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //因为设定分割线左边元素等于右边元素，或大于一，因此中位数=x 或中位数=（x+y）/2&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; nums1LeftMax &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; Integer.MIN_VALUE &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; nums1[i &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //此时nums1分割线左边没有元素了，因此nums1分割线左边元素的最大值要设置成无限小，在比较时直接选中nums2分割线的左边元素，其余同理&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; nums1RightMin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; m &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; Integer.MAX_VALUE &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; nums1[i];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; nums2LeftMax &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; ?&lt;/span&gt;&lt;span&gt; Integer.MIN_VALUE &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; nums2[j &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 1&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    int&lt;/span&gt;&lt;span&gt; nums2RightMin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; j &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; n &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; Integer.MAX_VALUE &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; nums2[j];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    //最后一步：中位数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; ((m &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; n) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt; ==&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      //偶数&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;double&lt;/span&gt;&lt;span&gt;)(Math.&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(nums1LeftMax, nums2LeftMax) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(nums1RightMin, nums2RightMin)) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; 2&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; Math.&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(nums1LeftMax, nums2LeftMax);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content:encoded></item><item><title>一些调试Web的小Tips</title><link>https://luckycc.cc/posts/morden-web-debug/</link><guid isPermaLink="true">https://luckycc.cc/posts/morden-web-debug/</guid><description>周末参加了FEDay，本文记录听Jecelyn的演讲后学到的小Tips💡，包含一些使用Chrome Devtools的小技巧</description><pubDate>Sat, 28 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;一些调试Web的小Tips&lt;/h1&gt;
&lt;p&gt;周末参加了FEDay，本文记录听&lt;strong&gt;Jecelyn&lt;/strong&gt;的演讲后学到的小Tips💡 &lt;a href=&quot;https://drive.google.com/file/d/1xCccv_U6jvBnq2SodLCia84jvX0f91Jj/view?usp=sharing&quot;&gt;Slide Link&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;浏览器如何读懂我们写的代码——sourcemap&lt;/h2&gt;
&lt;p&gt;浏览器只能解析HTML，js，css，但没办法解析其他的如ts，sass等语言。甚至React，Vue，Angular等前端框架，更不用说一些其他的SSR框架。&lt;/p&gt;
&lt;p&gt;Webpack，Vite，Rollup等构建工具，将浏览器看不懂的语言解析（打包）成为HTML，css，js，为了更快地提升浏览器的解析效率，打包出来的代码还会尽可能地控制体积。但是在浏览器中调试的时候，在console中点击报错代码，往往在source面板中就&lt;strong&gt;能够直接看到我们写的ts文件&lt;/strong&gt;，那么浏览器真的能看懂ts代码吗？&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;浏览器看不懂ts代码&lt;/strong&gt;，它只能读懂打包后的代码。我们之所以能在浏览器中调试源代码，是因为浏览器知道通过构建工具构建出来的代码，分别对应源代码的哪个文件的哪个位置。而这个用来将构建后浏览器看得懂但人看不懂的代码对应到人看得懂但浏览器看不懂的代码的东西，就是sourcemap。&lt;/p&gt;
&lt;h2&gt;Record 调试工具&lt;/h2&gt;
&lt;p&gt;试想有这么两个场景：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;你是一名前端开发者，你正在开发一个需求，现在已经进入了调试阶段。这个功能是在你填写10个表单项后，根据这10个表单项的值去向后端发送一个请求，后端返回给你一个表单项的值，你需要将这个值填入表单项中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;开始调试后，你每次调试都需要填10个值才能触发，你非常抓狂&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;你是一名后端开发者，你在使用“点点这点点那”的方式调试你前端同事的代码是否存在Bug。突然，你发现了一个Bug，你要给前端同事描述怎么触发这个Bug，但是很不幸，这个Bug的触发方式非常刁钻，它需要你将十个表单项设置成对应的值，才能触发。你告诉前端同事，你应该将表单项的值设置为xxxx，但是输入到第五个值的时候，你心态有点崩。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Chrome DevTools中有一个非常非常牛逼的功能，能够解决这两种场景下的痛点：&lt;strong&gt;Record&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;可以将自己的行为录制，并导出。但是我测试了一下，&lt;strong&gt;好像这个功能有点bug&lt;/strong&gt;，必须要保证元素在windows中，不然会导致崩溃？录制效果如下，可以将录制代码导出，发送给其他人。自己开发可以用一下，让别人用就算了，不然崩溃了更急了&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Emulate Focus Page&lt;/h2&gt;
&lt;p&gt;一些需要hover/active/focus状态来触发的元素是非常难调试的，通常的调试方法是为这个元素加一个状态，&lt;code&gt;addState&lt;/code&gt;，如图：&lt;/p&gt;
&lt;p&gt;我想调试Reference这个元素将鼠标放上去后弹出的下拉菜单样式，可以找到这个元素，给他加一个:focus状态&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;strong&gt;在判断一个元素是否有焦点事件之前，会先判断document是否有焦点事件&lt;/strong&gt;。如果document没有在被 &lt;code&gt;focus&lt;/code&gt;的状态下，元素的focus状态是不会响应的。但是如果打开控制台，会失去document的focus，如下：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这时候就需要打开&lt;strong&gt;Emulate Focus Page&lt;/strong&gt;来为document添加上focus，使得内部的元素也能响应对应的状态。&lt;/p&gt;
&lt;p&gt;打开方式有两种，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;最便捷的一种是&lt;strong&gt;Cmd+Shift+P&lt;/strong&gt;或者在&lt;strong&gt;右上角三个小点的菜单处&lt;/strong&gt;调出运行控制台，输入 &lt;code&gt;emulate a focused page&lt;/code&gt;，摁下回车即可&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第二种是在rendering处打开，找到这个选项把它勾上就可以了&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;在请求的时候可以加上响应头&lt;/h2&gt;
&lt;p&gt;添加请求头有相关的插件（ModHeader）能够做到，使用插件更加方便&lt;/p&gt;
&lt;p&gt;但原生调试工具也提供了支持，适合解决一些在个人项目中，需要后端解决的跨域问题，但后端还没来得及解决的情况。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</content:encoded></item><item><title>解决nginx访问动态接口报错404Not Found问题</title><link>https://luckycc.cc/posts/nginx-404/</link><guid isPermaLink="true">https://luckycc.cc/posts/nginx-404/</guid><description>计设做了一套招聘背调系统，前后端分别使用了Ant Design Vue与JFinal框架。想要将项目部署到服务器上，但是外部访问一直报错404Not Found，记录解决方法</description><pubDate>Fri, 30 Jul 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;问题描述&lt;/h2&gt;
&lt;p&gt;计设做了一套招聘背调系统，前后端分别使用了Ant Design Vue与JFinal框架。想要将项目部署到服务器上，但是外部访问一直报错404Not Found
&lt;/p&gt;
&lt;h2&gt;解决思路&lt;/h2&gt;
&lt;p&gt;把错误定位为：找不到动态接口，但是不知道是项目中出现了问题，还是经nginx代理后出现了问题。
因此要分别测试 项目本身的接口 和 nginx代理后的接口。&lt;/p&gt;
&lt;h5&gt;首先测试项目内接口：&lt;/h5&gt;
&lt;p&gt;在ubuntu端输入命令：curl &lt;a href=&quot;http://localhost:port/xxx/xxx&quot;&gt;http://localhost:port/xxx/xxx&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;这里我的接口是： curl http://localhost:20294/sys/login&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：

说明我的项目内接口是没有问题的。&lt;/p&gt;
&lt;h5&gt;再测试nginx代理后的接口：&lt;/h5&gt;
&lt;p&gt;再ubuntu中输入命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;curl http://localhost:8080/api/user/login&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：

这里提示找不到接口了，说明问题出在代理服务器nginx上，所以我们要去修改nginx的配置文件。&lt;/p&gt;
&lt;p&gt;按照其他博客的建议，我将nginx配置中这个地方加上了斜杠

重启服务器后，还是不行。&lt;/p&gt;
&lt;h4&gt;彻底解决&lt;/h4&gt;
&lt;p&gt;在当我不知道怎么办的时候，我突然发现我的服务器中，&lt;strong&gt;有两个nginx·····&lt;/strong&gt;
我在想是不是因为有两个nginx，修改的配置文件不是我启动的那个nginx。于是我把所有nginx配置文件都替换成我原始的配置文件，再重启。还是不行&lt;/p&gt;
&lt;p&gt;担心是两个nginx的问题，我把服务器中的所有nginx删除了。删除步骤（依次运行下面的步骤）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;ps aux|grep nginx  #查看nginx进程&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;kill -9 进程号      #杀死上一步中查询到的nginx（进程号在第二列）&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;find / -name nginx #找到nginx的文件地址&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;rm -rf xxx         #删除nginx所有文件&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后用weget安装新的nginx，然后按照原本安装步骤进行安装，修改配置文件后，再运行curl访问动态接口，突然就可以了!&lt;/p&gt;
&lt;p&gt;下面贴出我的nginx配置文件:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;user&lt;/span&gt;&lt;span&gt; root&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;#user  nobody;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;worker_processes&lt;/span&gt;&lt;span&gt;  4&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;#error_log  logs/error.log;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;#error_log  logs/error.log  notice;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;#error_log  logs/error.log  info;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;#pid        logs/nginx.pid;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;events&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    worker_connections&lt;/span&gt;&lt;span&gt;  1024&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;http&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    include&lt;/span&gt;&lt;span&gt;       mime.types&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    default_type&lt;/span&gt;&lt;span&gt;  application/octet-stream&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    log_format&lt;/span&gt;&lt;span&gt; main&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;$remote_addr - $remote_user [$time_local] &quot;$request&quot; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                      &apos;$status $body_bytes_sent &quot;$http_referer&quot; &apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;                      &apos;&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    server_names_hash_bucket_size&lt;/span&gt;&lt;span&gt; 128&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    client_header_buffer_size&lt;/span&gt;&lt;span&gt; 32k&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    underscores_in_headers&lt;/span&gt;&lt;span&gt; on&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    large_client_header_buffers&lt;/span&gt;&lt;span&gt; 4&lt;/span&gt;&lt;span&gt; 32k&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    client_max_body_size&lt;/span&gt;&lt;span&gt; 50m&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    #log_format  main  &apos;$remote_addr - $remote_user [$time_local] &quot;$request&quot; &apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    #                  &apos;$status $body_bytes_sent &quot;$http_referer&quot; &apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    #                  &apos;&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;&apos;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    #access_log  logs/access.log  main;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    sendfile&lt;/span&gt;&lt;span&gt;        on&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    #keepalive_timeout  0;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    keepalive_timeout&lt;/span&gt;&lt;span&gt;  65&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    tcp_nopush&lt;/span&gt;&lt;span&gt;          on&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fastcgi_connect_timeout&lt;/span&gt;&lt;span&gt; 300&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fastcgi_send_timeout&lt;/span&gt;&lt;span&gt;    300&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fastcgi_read_timeout&lt;/span&gt;&lt;span&gt;    300&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fastcgi_buffer_size&lt;/span&gt;&lt;span&gt;     64k&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fastcgi_buffers&lt;/span&gt;&lt;span&gt;     4&lt;/span&gt;&lt;span&gt;   64k&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fastcgi_busy_buffers_size&lt;/span&gt;&lt;span&gt; 128k&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fastcgi_temp_file_write_size&lt;/span&gt;&lt;span&gt; 256k&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    tcp_nodelay&lt;/span&gt;&lt;span&gt;         on&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    #gzip  on;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ######################################################&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    #############     麻雀配置地址    ###########&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    ######################################################&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    server&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        listen&lt;/span&gt;&lt;span&gt;       8080&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        server_name&lt;/span&gt;&lt;span&gt;  somename&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        location&lt;/span&gt;&lt;span&gt; /api/&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_pass&lt;/span&gt;&lt;span&gt; http://0.0.0.0:20294/&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;#映射到本地端口。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_redirect&lt;/span&gt;&lt;span&gt; off&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_set_header&lt;/span&gt;&lt;span&gt; Host&lt;/span&gt;&lt;span&gt; $http_host&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_set_header&lt;/span&gt;&lt;span&gt; X-Real-IP&lt;/span&gt;&lt;span&gt; $remote_addr&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_set_header&lt;/span&gt;&lt;span&gt; X-Forwarded-Proto&lt;/span&gt;&lt;span&gt; https&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_set_header&lt;/span&gt;&lt;span&gt; X-Forwarded-For&lt;/span&gt;&lt;span&gt; $proxy_add_x_forwarded_for&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            client_max_body_size&lt;/span&gt;&lt;span&gt; 200m&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_connect_timeout&lt;/span&gt;&lt;span&gt; 600&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_read_timeout&lt;/span&gt;&lt;span&gt; 600&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        location&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            root&lt;/span&gt;&lt;span&gt; /root/project-template/config/static&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            try_files&lt;/span&gt;&lt;span&gt; $uri&lt;/span&gt;&lt;span&gt; $uri&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; @router&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            index&lt;/span&gt;&lt;span&gt; index.html&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_set_header&lt;/span&gt;&lt;span&gt; X-Real-IP&lt;/span&gt;&lt;span&gt; $remote_addr&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_set_header&lt;/span&gt;&lt;span&gt; X-Forwarded-Proto&lt;/span&gt;&lt;span&gt; https&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_set_header&lt;/span&gt;&lt;span&gt; X-Forwarded-For&lt;/span&gt;&lt;span&gt; $proxy_add_x_forwarded_for&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            client_max_body_size&lt;/span&gt;&lt;span&gt; 200m&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_connect_timeout&lt;/span&gt;&lt;span&gt; 600&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            proxy_read_timeout&lt;/span&gt;&lt;span&gt; 600&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        location&lt;/span&gt;&lt;span&gt; @router&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            rewrite&lt;/span&gt;&lt;span&gt; ^.&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span&gt;/index.html&lt;/span&gt;&lt;span&gt; last&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是：配置动态访问api的时候，记得在最后加上斜杠&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;至此，问题完美解决。&lt;/p&gt;</content:encoded></item><item><title>由“object is not extensible”报错引发的思考及解决方案</title><link>https://luckycc.cc/posts/redux-toolkit-thinking/</link><guid isPermaLink="true">https://luckycc.cc/posts/redux-toolkit-thinking/</guid><description>使用Redux-Toolkit，由“object is not extensible”引发的思考及解决方案。本文介绍了Redux-Toolkit中的状态管理机制，介绍了Immer.js即它如何被应用在了Redux的状态管理中</description><pubDate>Tue, 20 Dec 2022 00: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;大致的编码逻辑是左上角的数据集选择器，控制全局UI的改变。比如&lt;strong&gt;左部的树形控件数据&lt;/strong&gt;，&lt;strong&gt;画布中的节点链接图&lt;/strong&gt;等等，都是根据当前所选的数据集来定的。这种组件间的状态复用，自然而然就想到把数据集作为一个状态来交给redux管理。&lt;/p&gt;
&lt;h3&gt;使用redux-toolkit&lt;/h3&gt;
&lt;p&gt;好的，现在开始查redux官方文档。因为刚学会react，教程中redux的store中使用的是 &lt;code&gt;createStore()&lt;/code&gt;创建的，但是这个方法目前已经弃用了，官方建议使用的是 &lt;code&gt;configureStore()&lt;/code&gt;。经过一番文档的查阅，开始使用 &lt;code&gt;createSlice()&lt;/code&gt;来重写reducer。&lt;/p&gt;
&lt;h4&gt;使用 &lt;code&gt;createSlice()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;这里直接贴上我这部分slice的&lt;strong&gt;错误代码&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;创建slice&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//  redux/optionSlice.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { createSlice } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;@reduxjs/toolkit&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { dataSets } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;../utils/getData&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { HIGHLIGHT } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;./constant&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; optionSlice &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; createSlice&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;option&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	initialState&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		data&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; dataSets[&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;case1&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		mode&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; HIGHLIGHT,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	reducers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		//这里对于state的解释在下文&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		changedata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			state.data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; action.payload;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		changemode&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			state.mode &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; action.payload;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; { changedata, changemode } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; optionSlice.actions;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; optionSlice.reducer;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;slice&lt;/code&gt;有两个导出，一个是在内部负责操作状态的action；一个是reducer&lt;/p&gt;
&lt;p&gt;我还有一另外一个 &lt;code&gt;selectionSlice&lt;/code&gt;负责管理其他的状态，这里考虑到篇幅就不给出了。&lt;/p&gt;
&lt;h4&gt;在 &lt;code&gt;index.js&lt;/code&gt;中融合两个silce&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//   redux/index.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; optionReducer &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;./optionSlice&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;//注意，这里引入的是slice中导出的reducer，slice有两个导出：reducer和action&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; selectionReducer &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;./selectionSlice&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; reducers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	option&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; optionReducer,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	selection&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; selectionReducer,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;在store.js配置store，并使用 &lt;code&gt;&amp;lt;Provider store={store}&amp;gt;&lt;/code&gt;让所有组件都可以使用redux中管理的状态&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;配置 &lt;code&gt;store&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// redux/store.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { configureStore } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;@reduxjs/toolkit&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { reducers } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;./index&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; store &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; configureStore&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	reducer&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; reducers, &lt;/span&gt;&lt;span&gt;//这里内置了combineReducer&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;添加 &lt;code&gt;Provider&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在App标签外部套上 &lt;code&gt;&amp;lt;Provider&amp;gt;&lt;/code&gt;标签&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { createRoot } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;react-dom/client&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; App &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;./App&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { BrowserRouter } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;react-router-dom&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { Provider } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;react-redux&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { store } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;./redux/store&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;createRoot&lt;/span&gt;&lt;span&gt;(document.&lt;/span&gt;&lt;span&gt;getElementById&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;root&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;&lt;/span&gt;&lt;span&gt;Provider&lt;/span&gt;&lt;span&gt; store&lt;/span&gt;&lt;span&gt;={&lt;/span&gt;&lt;span&gt;store&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;App&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&amp;lt;/&lt;/span&gt;&lt;span&gt;Provider&lt;/span&gt;&lt;span&gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;绘制节点链接图&lt;/h3&gt;
&lt;p&gt;用户选择一份数据集，就会把这份数据集交给redux管理，在其他组件中如果想要取用数据集，使用 &lt;code&gt;useSelector(state =&amp;gt; state.option.data)&lt;/code&gt;即可取用。&lt;strong&gt;&lt;code&gt;&amp;lt;font color=&quot;red&quot;&amp;gt;&lt;/code&gt;问题就发生在这一步 &lt;code&gt;&amp;lt;/font&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我先简述一下我的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// componnets/Canvas/index.jsx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; Canvas&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useSelector&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; state.option.data);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	useLayoutEffect&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		initCanvas&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;//drawLayout&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}, [data]);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; initCanvas&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		// .....append canvas&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const&lt;/span&gt;&lt;span&gt; nodes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.nodes;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		const&lt;/span&gt;&lt;span&gt; links &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.links;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		//append circle,line......&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		let&lt;/span&gt;&lt;span&gt; simulation &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; d3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;forceSimulation&lt;/span&gt;&lt;span&gt;(nodes)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;force&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				&apos;&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				d3&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;forceLink&lt;/span&gt;&lt;span&gt;(links)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;						return&lt;/span&gt;&lt;span&gt; d.mgmt_ip;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;strength&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					.&lt;/span&gt;&lt;span&gt;distance&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			//......some force option&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;tick&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				//refresh canvas&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; className&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;container&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;gt; &amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个代码很简单，我在 &lt;code&gt;useLayoutEffect()&lt;/code&gt;这个钩子里编写了一个画布初始化函数 &lt;code&gt;initCanvas()&lt;/code&gt;。目的是让组件挂载前，先在一个 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;中添加一个canvas，并绘制出数据。&lt;/p&gt;
&lt;h3&gt;报错重现&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;font color=&quot;maroon&quot;&amp;gt;&lt;/code&gt;结果这个代码直接报红了，报了一个我从没见过的错误: “Uncaught TypeError:Cannot add property vx,object is extensible”&lt;code&gt;&amp;lt;/font&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;从这个报错信息很容易就能知道，是在我设置力模拟器时，调用 &lt;code&gt;d3.forceLink(links)&lt;/code&gt;绑定连边，和绑定节点时，无法像边数据和点数据中添加vx，vy等属性导致的。&lt;/p&gt;
&lt;p&gt;为了进一步验证这个特点，我用以下代码验证了我拿到的数据是否真的不可拓展&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; nodes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.nodes;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; links &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.links;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;nodes.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(node.&lt;/span&gt;&lt;span&gt;isExtensible&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;links.&lt;/span&gt;&lt;span&gt;forEach&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(link.&lt;/span&gt;&lt;span&gt;isExtensible&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;毫无意外，控制台输出了清一色的 &lt;code&gt;false&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;h3&gt;采用拷贝对象的方式解决（笨方法）&lt;/h3&gt;
&lt;p&gt;所以这个问题可以基本确定是因为我的数据不可拓展造成的，虽然不知道为什么。但是解决这个的办法无非就是让我的数据能够被拓展。但是搜了半天解除不可拓展性的办法，找不到。于是只能采用拷贝对象的方式，拷贝一份新的对象。&lt;/p&gt;
&lt;p&gt;拷贝分两种方式：浅拷贝与深拷贝。在有指针的情况下，&lt;strong&gt;浅拷贝只是增加了一个指针指向已经存在的内存&lt;/strong&gt;，而深拷贝就是&lt;strong&gt;增加一个指针并且申请一个新的内存&lt;/strong&gt;，&lt;strong&gt;使这个增加的指针指向这个新的内存&lt;/strong&gt;。显然，我们需要使用深拷贝，申请一个新的内存存放拷贝的对象。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nodes&lt;/code&gt;与 &lt;code&gt;links&lt;/code&gt;数组中存放的obj如图所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;nodes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; 	 	&lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;xxxx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;role&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;xxxx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;xxxx&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span&gt;links&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; 	 	&lt;/span&gt;&lt;span&gt;source&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;xxxx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;xxxx&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此我们使用对象拓展符 &lt;code&gt;{...node},{...link}&lt;/code&gt;即可完成深拷贝，具体代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; newNode &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nodes.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ({ &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;node }));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; newLink &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; links.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; ({ &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;link }));&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着我们使用newNode和newLink替换原来的nodes和links，就OK了。&lt;/p&gt;
&lt;h3&gt;把redux中存储的数据对象替换为数据名&lt;/h3&gt;
&lt;p&gt;上面的办法显然很蠢······。&lt;strong&gt;我慢慢开始意识到这个对象的不可拓展性很可能是redux帮我处理的&lt;/strong&gt;，因为&lt;strong&gt;我们在redux中存放的数据应该由对应的reducer来进行更改&lt;/strong&gt;，如果外部能够更改会导致UI组件中获取的状态出现错误。&lt;/p&gt;
&lt;p&gt;因为发现这个问题已经很晚了，我没有急着去验证的想法是不是对的，因为我想赶紧把我的蠢方法换掉，让我的程序看起来别那么烂。我之前建立了一个函数帮我提供数据集，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; case1 &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;../assets/case1.json&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; case2 &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;../assets/case2.json&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; case3 &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;../assets/case3.json&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; function&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	const&lt;/span&gt;&lt;span&gt; datasets &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		case1,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		case2,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		case3,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; datasets;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; dataSets &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; generate&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这么做的目的是我在组件中直接使用 &lt;code&gt;import {dataSets} from &quot;../util/getData.js&quot;&lt;/code&gt;就能获取到全部数据集了。&lt;/p&gt;
&lt;p&gt;写到这，应该很明白了。正确的思路应该是将数据集的名字，如case1，case2，case3…交给redux来管理，用户每次切换数据集，就通知reducer更改当前的数据集名称。在组件中如果想要使用数据的话就以下代码来获取。这么做显然比把整份数据交给redux管理更加合理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; {dataSets} &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &quot;&lt;/span&gt;&lt;span&gt;../util/getData.j&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; Component&lt;/span&gt;&lt;span&gt;(){&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; dataName &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; useSelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; state.option.dataName)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  const&lt;/span&gt;&lt;span&gt; data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; dataSets[dataName]&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  //func body&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  return&lt;/span&gt;&lt;span&gt; ...&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里贴上一个正确代码，和之前相比，我把交给redux管理的状态从 &lt;code&gt;data&lt;/code&gt;换成了 &lt;code&gt;dataName&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// redux/optionSlice.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { createSlice } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;@reduxjs/toolkit&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { HIGHLIGHT } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;./constant&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;/**data option */&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; optionSlice &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; createSlice&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;option&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	initialState&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		dataName&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;case1&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		mode&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; HIGHLIGHT,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	reducers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		changedata&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			state.dataName &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; action.payload;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		changemode&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			state.mode &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; action.payload;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; { changedata, changemode } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; optionSlice.actions;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; default&lt;/span&gt;&lt;span&gt; optionSlice.reducer;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实已经发现区别了，在修改之前，我把整个数据集 &lt;code&gt;data = {nodes:[...],links:[...]}&lt;/code&gt;作为了整个状态存放到了redux中。而修改之后，我只存了&lt;strong&gt;数据集的名称&lt;/strong&gt;，使用的时候用这个&lt;strong&gt;名称&lt;/strong&gt;去一个存放了所有dataSets的地方取。这显然是一种更加合理的编码方式。&lt;/p&gt;
&lt;h2&gt;产生原因分析&lt;/h2&gt;
&lt;h4&gt;immer.js =&amp;gt; 不可变数据结构&lt;/h4&gt;
&lt;p&gt;基本能够初步确定redux-toolkit在返回新状态值的时候，设置了返回的obj是不可扩展的。为了验证我的猜想，我去redux-toolkit官网找到了下面这篇 &lt;code&gt;Writing Reducers with Immer&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Immer，Immer是什么？读这篇文章第一句话&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Redux Toolkit’s &lt;a href=&quot;https://redux-toolkit.js.org/api/createReducer&quot;&gt;&lt;code&gt;createReducer&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://redux-toolkit.js.org/api/createSlice&quot;&gt;&lt;code&gt;createSlice&lt;/code&gt;&lt;/a&gt; automatically use [Immer]
(&lt;a href=&quot;https://immerjs.github.io/immer/&quot;&gt;https://immerjs.github.io/immer/&lt;/a&gt;) internally to let you write simpler immutable update logic using “mutating” syntax. This helps simplify most reducer implementations.&lt;/p&gt;
&lt;p&gt;译文：Redux Toolkit&lt;a href=&quot;https://redux-toolkit.js.org/api/createReducer&quot;&gt;&lt;code&gt;createReducer&lt;/code&gt;&lt;/a&gt;并在内部&lt;a href=&quot;https://redux-toolkit.js.org/api/createSlice&quot;&gt;&lt;code&gt;createSlice&lt;/code&gt;&lt;/a&gt;自动使用&lt;a href=&quot;https://immerjs.github.io/immer/&quot;&gt;Immer&lt;/a&gt;让您使用“mutating”语法编写更简单的不可变的更新逻辑。这有助于简化大多数 reducer 实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;immutable update logic&lt;/code&gt;不可变的更新逻辑，我想我找到答案了。于是我去google了 &lt;code&gt;immer.js&lt;/code&gt;，&lt;/p&gt;
&lt;p&gt;在它的中文官方文档中，有一段这么介绍的话：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Immer can be used in any context in which immutable data structures need to be used. For example in combination with React state, React or Redux reducers, or configuration management. Immutable data structures allow for (efficient) change detection: if the reference to an object didn’t change, the object itself did not change. In addition, it makes cloning relatively cheap: Unchanged parts of a data tree don’t need to be copied and are shared in memory with older versions of the same state.&lt;/p&gt;
&lt;p&gt;译文：&lt;strong&gt;Immer 可以在需要使用不可变数据结构的任何上下文中使用&lt;/strong&gt;。例如与 React state、React 或 &lt;strong&gt;Redux reducers&lt;/strong&gt; 或者 configuration management 结合使用。&lt;strong&gt;不可变的数据结构允许（高效）的变化检测&lt;/strong&gt;：如果对对象的引用没有改变，那么对象本身也没有改变。此外，它&lt;strong&gt;使克隆对象相对便宜&lt;/strong&gt;：&lt;strong&gt;数据树的未更改部分不需要复制，并且在内存中与相同状态的旧版本共享&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;看完这两段话，比较抽象，直接看官方给的代码示例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;有一个Todo列表，我们要对它进行更新&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; baseState &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;Learn TypeScript&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		done&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;Try Immer&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		done&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;不使用Immer&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;//不使用immer&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; nextState &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; baseState.&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// 浅拷贝数组&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;nextState[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	// 替换第一层元素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	...&lt;/span&gt;&lt;span&gt;nextState[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;// 浅拷贝第一层元素&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	done&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;// 期望的更新&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 因为 nextState 是新拷贝的, 所以使用 push 方法是安全的,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// 但是在未来的任意时间做相同的事情会违反不变性原则并且导致 bug！&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;nextState.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({ title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;Tweet about it&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用Immer&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; produce &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;immer&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; nextState &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; produce&lt;/span&gt;&lt;span&gt;(baseState, (&lt;/span&gt;&lt;span&gt;draft&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	draft[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;].done &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	draft.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;({ title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &apos;&lt;/span&gt;&lt;span&gt;Tweet about it&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;从上可以看出，使用Immer会把更改应用当前的草稿 &lt;code&gt;draft&lt;/code&gt;上，它是当前状态的代理，一旦我们完成了所有的更改，Immer会根据 &lt;code&gt;draft&lt;/code&gt;上 &lt;code&gt;state&lt;/code&gt;的更改生成新的 &lt;code&gt;nextState&lt;/code&gt;,工作原理示意图如下：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;引用官方文档中的一段话，来解释Immer的作用&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Using Immer is like having a personal assistant. The assistant takes a letter (the current state) and gives you a copy (draft) to jot changes onto. Once you are done, the assistant will take your draft and produce the real immutable, final letter for you (the next state).&lt;/p&gt;
&lt;p&gt;使用 Immer 就像拥有一个私人助理。助手拿一封信（当前状态）并给您一份副本（草稿）以记录更改。完成后，助手将接受您的草稿并为您生成真正不变的最终信件（下一个状态）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个“私人助理”其实是一个代理对象Proxy，我在redux中也做了进一步的验证。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// /redux/optionSlice.js&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;reducers: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        changedata: ((&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(state)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            state.dataName &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; action.payload&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        }),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        changemode: ((&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;            state.mode &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; action.payload&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;        })&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我在代码中打印了state，并在控制台查看了它的输出，确实是一个Proxy对象。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;Redux-toolkit中 &lt;code&gt;createSlice()&lt;/code&gt;的使用&lt;/h3&gt;
&lt;p&gt;在浅了解了Immer.js后，我回到官方文档中阅读剩余部分。并整理了以下对我可能有帮助的点&lt;/p&gt;
&lt;h4&gt;状态的不可变性，为什么会引入Immer.js&lt;/h4&gt;
&lt;p&gt;要分析状态的不可变性，首先我们要引入的一个问题是Redux中不可改变状态的几个原因。官方文档中列出了五条原因，但我认为最重要的是第一条：&lt;strong&gt;会导致bug，例如UI无法正确更新显示最新值&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那么redux不能更改原始状态，我们如何返回更新后的状态呢？答案是在Reducer中只能拷贝原始值，修改副本并返回副本。如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// ✅ This is safe, because we made a copy&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	...&lt;/span&gt;&lt;span&gt;state,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 123&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这也让我想到了之前在写类组件时，必须要用拷贝的方式修改，如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;setState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  {&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;state,key:newValue}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我猜和Immer.js也有关系。&lt;/p&gt;
&lt;p&gt;这样修改当然OK没有问题，&lt;strong&gt;但是如果状态之中嵌套了许多层，那么我们需要对每一层都进行拷贝&lt;/strong&gt;，这样的代码维护方式显然是灾难一样的存在！这里我贴上官网给的例子。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;手动编写不可变的更新逻辑很困难，并且&lt;strong&gt;在 reducer 中意外改变状态是 Redux 用户最常犯的一个错误&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; handwrittenReducer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		...&lt;/span&gt;&lt;span&gt;state,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		first&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			...&lt;/span&gt;&lt;span&gt;state.first,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			second&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				...&lt;/span&gt;&lt;span&gt;state.first.second,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				[action.someId]&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					...&lt;/span&gt;&lt;span&gt;state.first.second[action.someId],&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;					fourth&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; action.someValue,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;				},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;			},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;		},&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;	};&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以，引入了Immer，Immer是一个库，简化了编写不可变更新逻辑的过程。Immer的工作流程我们在上文中已经介绍过了，这里不做过多赘述，值得注意的是，ReactToolkit的 &lt;code&gt;createReducer&lt;/code&gt;和 &lt;code&gt;createSlice&lt;/code&gt;都在内部使用了Immer。上文我也已经验证过了 &lt;code&gt;state&lt;/code&gt;是一个代理。&lt;/p&gt;
&lt;h4&gt;更改状态的两种方式：&lt;code&gt;reset&lt;/code&gt;与 &lt;code&gt;replace&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;reset&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// reset 修改&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;reducers: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    todoDeleted&lt;/span&gt;&lt;span&gt;(state, action.payload) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // Construct a new array immutably&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      const&lt;/span&gt;&lt;span&gt; newTodos &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; state.todos.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;todo&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; todo.id &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; action.payload)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // &quot;Mutate&quot; the existing state to save the new array&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      state.todos &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; newTodos&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;replace&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;// replace 替换&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;reducers: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    todoDeleted&lt;/span&gt;&lt;span&gt;(state, action.payload) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // Construct a new result array immutably and return it&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      return&lt;/span&gt;&lt;span&gt; state.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;todo&lt;/span&gt;&lt;span&gt; =&amp;gt;&lt;/span&gt;&lt;span&gt; todo.id &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; action.payload)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有一个易错的地方，就是有一些修改函数会有默认返回值，那么在修改状态后有一个返回值，reducer就不知道应该使用哪个值作为最新的状态了。如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;reducers: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // ❌ ERROR: mutates state, but also returns new array size!&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    brokenReducer: (&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; state.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(action.payload),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // ✅ SAFE: the `void` keyword prevents a return value&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fixedReducer1: (&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; void&lt;/span&gt;&lt;span&gt; state.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(action.payload),&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    // ✅ SAFE: curly braces make this a function body and no return&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    fixedReducer2: (&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      state.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(action.payload)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;如何输出当前状态&lt;/h4&gt;
&lt;p&gt;想要从reducer中记录正在进行的状态以查看它在更新时的样子，这个场景是很常见的。但不幸的是，直接输出 &lt;code&gt;state&lt;/code&gt;是一个 &lt;code&gt;Proxy&lt;/code&gt;对象。为了解决这个问题，Immer提供了一个函数 &lt;code&gt;current()&lt;/code&gt;，如果需要查看状态可以使用它&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;reducers: {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    todoToggled&lt;/span&gt;&lt;span&gt;(state, action) {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // ❌ ERROR: logs the Proxy-wrapped data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(state)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      // ✅ CORRECT: logs a plain JS copy of the current data&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      console.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;current&lt;/span&gt;&lt;span&gt;(state))&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  },&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;为什么会引入Immer？&lt;/h4&gt;
&lt;p&gt;下面三点是我对官方文档的一个总结与复述&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;使用Immer的优点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Immer极大简化了不可变的更新逻辑&lt;/li&gt;
&lt;li&gt;减少了reducer更新状态的编写错误。引入Immer后，无需创建副本，直接进行修改即可。（相当于你把修改的工作交给了一个代理，由代理帮你进行修改）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Immer在性能上的权衡&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无需考虑，reducer几乎从来都不是Redux应用中的性能瓶颈&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;是否考虑未来将Immer设置为可选项？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;我有预感很多人在简单看了Redux-toolkit文档就拿去用了以后，都会给它们提Issue。因为这个对象的不可变性稍微不留意就会出错（但是习惯了它们的写法以后其实效率提升很多）。官方文档中也给出了为什么不打算将Immer设置为可选项的理由，它们说React-toolkit的架构是通过直接导入Immer来实现的，需要在应用程序加载期间立即同步使用Immer。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;And finally: &lt;strong&gt;Immer is built into RTK by default because we believe it is the best choice for our users!&lt;/strong&gt; We &lt;em&gt;want&lt;/em&gt; our users to be using Immer, and consider it to be a critical non-negotiable component of RTK. The great benefits like simpler reducer code and preventing accidental mutations far outweigh the relatively small concerns.&lt;/p&gt;
&lt;p&gt;最后：**Immer 默认内置在 React-toolkit 中，因为我们相信它是我们用户的最佳选择！**我们希望我们的用户使用 Immer，并将其视为 React-toolkit 的关键组件。更简单的 reducer 代码和防止意外突变等巨大好处，远远超过了那些可以被忽视的问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;思考&lt;/h2&gt;
&lt;p&gt;这是我解决问题的完整过程，最近在做项目，写了好久的文档，好久没有沉淀自己的代码能力了。碰巧周日，碰巧遇到了一个值得记录的问题，赶紧把自己的思考过程落实在了文字。&lt;/p&gt;
&lt;p&gt;从组件中选择状态升格为全局这是一个值得思考的问题，我也认为这是很考验一个React写手能力的工作。最近刚入门React，浅记录一下解决问题的全过程。&lt;/p&gt;</content:encoded></item><item><title>半月板手术记录</title><link>https://luckycc.cc/thoughts/10_meniscus_surgery/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/10_meniscus_surgery/</guid><pubDate>Wed, 06 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在2024-10-31，十月份的最后一天，我完成了我人生中的第一个全麻手术：半月板撕裂修复关节镜手术。&lt;/p&gt;
&lt;p&gt;这会是一篇很长的文章，我在文章会记录手术前中后期的全过程，以及心态变化。&lt;/p&gt;
&lt;p&gt;术后康复是一个非常漫长的过程，或许会再写一篇来讲讲。&lt;/p&gt;
&lt;h1&gt;病因&lt;/h1&gt;
&lt;p&gt;公司里有一群热爱篮球的同事，会在每周的&lt;strong&gt;一三五&lt;/strong&gt;这三天的中午，约个全场。&lt;/p&gt;
&lt;p&gt;在 2024-08-28 ，至今印象非常深刻，不用查看日历就知道是一个双周的周三，活动日，晚上例行会有两小时的全场球局。&lt;/p&gt;
&lt;p&gt;2024-08-28 就是这么一天，中午和晚上都有球局。因为那段时间比较贪玩，我一天打了两场球。&lt;/p&gt;

&lt;p&gt;B站有个 UP 主叫&lt;a href=&quot;https://space.bilibili.com/525873105&quot;&gt;徐远征 Oliver&lt;/a&gt;，以防守能力著名。那段时间我很喜欢他的视频，也在刻意训练自己的防守脚步，力量和体能。&lt;/p&gt;
&lt;p&gt;打过篮球的兄弟应该都知道，篮球中的防守脚步一般都是滑步和交叉比较多。这些动作都需要膝盖进行制动，急停，这一系列动作都是需要非常强大的股四头肌/和整个腿部后侧力量作为支撑的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;滑步：下面这张图中的12号队员，会通过侧向或纵向移动去阻拦进攻队员，或尝试干扰其投射&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;h1&gt;噩耗&lt;/h1&gt;
&lt;p&gt;到了 2024-10-18 ，当时膝盖其实已经没有不舒服了，只是有某些角度会有些弹响。我能很明显地感觉到大腿下方的骨头卡到了某块软骨中的缝隙里，然后崩一下突然复位。&lt;/p&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;p&gt;医生还挺好，我问他我是不是半月板撕裂了，他说他从来不猜，等结果出来照着结果说。&lt;/p&gt;
&lt;p&gt;中午拍完核磁，下午就拿到结果了，我的噩梦从此刻开始。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;诊断结果为：&lt;strong&gt;左膝外侧半月板移位并撕裂，右膝外侧半月板撕裂&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;医生看完我的结果，说”小伙子你这个左边撕裂得蛮严重啊，要考虑马上手术了。你还很年轻，半月板缝合是有价值的“。后续就是交代了一下如果找他做手术可以当周安排住院手术，提前告知一下父母就可以了。&lt;/p&gt;
&lt;p&gt;一听到手术这两个字，我已经被吓得不知道说什么好。我问医生费用大概是多少，他说这取决于要缝几针，一针大概三四千块钱。大概需要准备个三四针的量。&lt;/p&gt;
&lt;p&gt;我天真地以为一万内就能给我一个健康的半月板，后来才发现这是除去基本手术费用2w后，缝合一针就需要三千到四千元。&lt;/p&gt;
&lt;p&gt;出了医院我心情复杂，一方面是不愿意接受这个事实；另一方面是感觉自己刚实习赚了一点钱，没有办法享受了，还需要家里补贴一些，非常心疼；还有一方面很后悔，我恨自己太贪玩了，明明膝盖已经发出伤病信号了还是没注意，继续打球，伤成现在这样。&lt;/p&gt;
&lt;p&gt;离开医院后，我立马联系了之前给我做过康复的康复医生刘Sir，想让她帮忙看看情况。因为需要手术，所以想多听听其他的建议。&lt;/p&gt;

&lt;p&gt;刘Sir更是雪上加霜，拿起片子不超过5秒，给出了相同的结论：左膝手术，右膝可以做有创保守治疗。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📒 科普：有创保守治疗，其实就是打PRP，PRP就是对人体的血液进行抽取提纯血清，注射至患处，进行治疗）。半月板分为红区，白区，和红白区。如果是红区损伤，那通过保守治疗自己能够愈合，如果是白区损伤，由于没有血液经过，无法主动愈合，损伤部分如果会对关节造成活动障碍/卡压需要进行切除。红白区我不是特别了解，估计就是可以打PRP进行治疗吧。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;离开康复医院，我坐在马路边上开始大哭，我觉得人生的至暗时刻不过如此。毕业论文，秋招，感情问题，手术，术后康复。一大堆破烂事压得我喘不过气。&lt;/p&gt;
&lt;p&gt;平复心情后，给妈妈打了个电话，告知了一下情况。&lt;/p&gt;
&lt;p&gt;打完电话，坐在路边继续平复了一下心情，打开b站小红书看了看病友们的手术记录，就回寝室了。&lt;/p&gt;
&lt;h1&gt;寻医&lt;/h1&gt;
&lt;p&gt;了解了一下半月板撕裂修复手术，是一个微创关节镜手术。医生会在膝盖处开两个小口子，把一个叫关节镜的设备从这两个口子伸进膝盖里面，各种操作。&lt;/p&gt;
&lt;p&gt;膝盖手术都是三分手术，七分康复。因为以后还想打球，所以我没有选择回家做手术，我和妈妈说想找大医院比较不错的医生给我做手术，决定留在长沙在湘雅手术。&lt;/p&gt;
&lt;p&gt;妈妈因为担心手术的风险，一直在寻找保守治疗的方案。我告诉她手术就是为了之后还能开心快乐地运动，打球，我离不开运动。期间我们还爆发了争吵，因为事情实在太多了，我的情绪再次崩溃。&lt;/p&gt;

&lt;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;一个是附一的肖文峰医生，一号难求，给同门的甲方的妈妈做过手术，如果要看只能挂500元的专家号。&lt;/p&gt;
&lt;p&gt;另一个是附二的朱威宏医生，一号难求，在美国梅奥诊所（非常厉害的美国总统的御用医院诊所）进修过。&lt;/p&gt;
&lt;p&gt;朱医生刘Sir帮我找了关系（非常感谢刘Sir），可以插一个号，但是我们沟通有些失误，最后没看成。&lt;/p&gt;
&lt;h2&gt;湘雅附一 - 徐大启&lt;/h2&gt;
&lt;p&gt;我先去附一看了运动医学科的&lt;strong&gt;徐大启&lt;/strong&gt;，这是一个在长沙比较有名的医生，但据说&lt;strong&gt;康复的方案会稍微激进一些&lt;/strong&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;p&gt;徐医生挺幽默哈，回了句“准备钱就行了”。&lt;/p&gt;
&lt;p&gt;最后我确认了一下如果要手术是不是徐医生亲自做，他说是的，我就回去了。&lt;/p&gt;
&lt;h2&gt;湘雅附二 - 吴韧&lt;/h2&gt;
&lt;p&gt;随后我去看了附二骨科的吴韧医生，仍然是一个非常有名的医生。抖音上能搜到，大约10w粉，做前叉手术比较厉害。&lt;/p&gt;
&lt;p&gt;吴医生给我的印象非常棒，很耐心，绅士。有一个非常深的印象是看病那天我是倒数第二个号，轮到我的时候那层楼的医生几乎都走完了，就剩吴医生还在科室。&lt;/p&gt;
&lt;p&gt;仍然是重复了若干次的对话：&lt;/p&gt;
&lt;p&gt;我：“是不是挺严重的，需要手术吗”&lt;/p&gt;
&lt;p&gt;吴医生：“这个看你的体感，但是报告上确实达到了手术指征，建议手术”&lt;/p&gt;
&lt;p&gt;我：“做完了我之后还能运动吗”&lt;/p&gt;
&lt;p&gt;吴医生：”做好康复没什么问题，你看NBA好多球星没了半月板照样打球，有啥不行的”&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;h1&gt;术前准备&lt;/h1&gt;
&lt;p&gt;从医院回来的第一件事，我去找导师聊了毕业论文的选题。&lt;/p&gt;
&lt;p&gt;果然，换了一个题目，但好在这个研究方向是一个比较成熟的研究方向，做法都大差不差，就是场景和问题比较难定义。&lt;/p&gt;
&lt;p&gt;随后，我分别做了以下准备：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;住宿：联系宿管看看有没有空寝室，宿管让我联系一下宿管办。宿管办的老师真的非常好，给了我一间无障碍宿舍。宿舍里面不是上床下桌，放了两张平床，方便起居。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;术前用品：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;膝盖支具，咸鱼买了二手的自提，来回共计坐了两个小时的公交，花费160&lt;/li&gt;
&lt;li&gt;拐杖，淘宝买的，花费60&lt;/li&gt;
&lt;li&gt;弹力带，用作术后康复&lt;/li&gt;
&lt;li&gt;坐便椅，放寝室术后上厕所用&lt;/li&gt;
&lt;li&gt;床上小桌板，放寝室术后工作用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;保险：学生医保暂时没有询问，应该只能报销住院部分&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;住院衣物与洗漱用品&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1W元预缴金&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;住院日&lt;/h1&gt;
&lt;p&gt;在 2024-10-28 的早晨，我还没有收到住院部的电话，但先前他们答复预计今天能够入住。于是我打了个电话给住院部，那边表示如果今天下午四点半前能够到达，就能够正常安排住院。&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;ul&gt;
&lt;li&gt;
&lt;p&gt;X线平扫，检查胸部骨头，腿部骨头的。&lt;/p&gt;
&lt;p&gt;结果一切正常，就是两条腿发现相差了2毫米。&lt;/p&gt;
&lt;p&gt;我在群里和群友打趣说我两条腿不一样长，两只眼睛不一样大，两边眼皮也不一样，就tm膝盖是一样的，半月板都撕裂了 😄。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;空腹抽血，检查术前各项指标的。&lt;/p&gt;
&lt;p&gt;空腹抽血在29号早晨，六点钟左右就有一个实习护士小姐姐过来抽血。我一看要抽九管，人都吓晕了，再一看实习护士，更害怕了。&lt;/p&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;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;2024-10-30，妈妈从海南飞过来，sr从上海连夜硬座赶到长沙陪我手术。给我整得&lt;strong&gt;又感动又愧疚&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;都24岁的大人了，还让人这么操心。&lt;/p&gt;
&lt;h1&gt;手术日&lt;/h1&gt;
&lt;p&gt;2024-10-31，终于等到了这一天，手术日。&lt;/p&gt;
&lt;p&gt;非常紧张，换上手术服拍了张照。好在下身可以穿自己的短裤，不然2XL的手术服真的要把我勒死了。&lt;/p&gt;

&lt;p&gt;30号那天下午，麻醉医生过来告知麻醉风险，并交代我凌晨12点后不要再饮食饮水。&lt;/p&gt;
&lt;p&gt;麻醉方式选择了全麻，没有局麻的选项，据说是为了更安全。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📒 科普：局麻通常会采用腰麻，会在腰部打神经阻滞，此类麻醉方式据医生说风险反而更高，因为怕伤到脊柱。&lt;/p&gt;
&lt;p&gt;全麻通常是静脉麻醉，&lt;strong&gt;体验挺神奇的（下文会介绍）&lt;/strong&gt;。全麻的风险主要在于麻醉后病人会停止“自主呼吸”，需要插一根喉管，并由呼吸设备辅助呼吸，并由麻醉医生全程监控病人生命体征，其实就相当于把自己的生命交给麻醉医生了。&lt;/p&gt;
&lt;p&gt;全麻手术病人手术过程中全程无感知，好像手术这几个小时被偷走了一样，“睡了一觉”手术就结束了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我的手术被安排到了后面几台，在睡前我&lt;strong&gt;喝了一升水，吃了三个橙子、一个柿子、四个麻薯&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;第二天醒来还是没有逃过扎针的命运，从起床的那一刻开始，就开始挂盐水，糖水，所以就算没吃东西，我也不会感觉到饥饿。&lt;/p&gt;
&lt;p&gt;到了中午13:00，护士让我带上报告，手术室过来接人了。一个穿着绿色衣服的大叔推着一张平板床就过来了。&lt;/p&gt;
&lt;p&gt;我躺在平板床上东张西望，被推到手术室门口临时去上了个厕所，真的太紧张太害怕了。&lt;/p&gt;

&lt;p&gt;随后，我被推到一个小房间，由手术室的护士在我的手臂上打上留置针。&lt;/p&gt;
&lt;p&gt;那个护士一眼就是实习生，操作还是不太熟练，老是被旁边的带教老师批评，我看她有点紧张，故作镇定和她说了声没事的，谁都有第一次，大胆扎。我看着大概5cm的一根针头全部插进到我的皮肤，更加紧张了。&lt;/p&gt;
&lt;p&gt;扎好了留置针后，有一个医生过来询问姓名，问我知不知道今天要做什么手术，我说：左膝半月板关节镜缝合手术。给医生惊到了，问我是不是学医的，我和他开了几句玩笑，就被推到麻醉室了。术前只能通过这种小幽小默来给自己增加信心了。&lt;/p&gt;
&lt;h2&gt;麻醉&lt;/h2&gt;
&lt;p&gt;下面我要讲讲全麻的神奇体验，真的非常神奇。&lt;/p&gt;
&lt;p&gt;首先有一个医生用静脉彩超来看我大腿上的血管，他说要找腿上的神经，看看血管。我以为要从腿上扎一根针进去打麻醉，我问他疼不疼，他说不疼，没什么感觉。&lt;/p&gt;
&lt;p&gt;后来来了个年纪较大的医生，在我的留置针上推了一管药。&lt;/p&gt;
&lt;p&gt;医生：“现在在推镇定剂，可能会有点头晕哈”。&lt;/p&gt;
&lt;p&gt;我：”好的“。&lt;/p&gt;
&lt;p&gt;随后确实有点头晕，有点像喝多了微醺的感觉。&lt;/p&gt;
&lt;p&gt;医生又过来推了一管药，透明的。&lt;/p&gt;
&lt;p&gt;我问医生：”这是啥“。 医生没说话&lt;/p&gt;
&lt;p&gt;接着，医生又过来推了一管药。&lt;/p&gt;
&lt;p&gt;我印象非常深刻，最后这管药是白色的。因为我问了医生一句：”医生，怎么是白色的？“&lt;/p&gt;
&lt;p&gt;下一秒钟，我就睡着了。再下一秒钟，手术就结束了。&lt;/p&gt;
&lt;h2&gt;术中&lt;/h2&gt;
&lt;p&gt;没有感觉。不过我是同批患者中最年轻的，年纪轻轻经历丰富。&lt;/p&gt;

&lt;h2&gt;苏醒&lt;/h2&gt;
&lt;p&gt;麻醉医生一边拍我，一边告诉我手术结束了，让我咳嗽，呼吸。&lt;/p&gt;
&lt;p&gt;医生说我的静息心率非常低，只有40。问我是不是运动员，因为麻药还没醒，我在那儿胡言乱语半天，听到医生这么问心里还挺开心的。&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;h1&gt;术后&lt;/h1&gt;
&lt;p&gt;术后两小时内不能睡觉，但非常想睡觉。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📒科普：为什么术后两小时不能睡觉？麻醉会抑制呼吸中枢，麻醉后2h内保持清醒是为了防止患者呼吸遗忘而导致窒息。&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;h2&gt;肿胀&lt;/h2&gt;
&lt;p&gt;术后前两天，能明显地看出来术腿非常肿胀，这是正常的。因为术中为了更好地观察关节缝隙，会忘里边注入某种液体，把关节撑开。&lt;/p&gt;
&lt;p&gt;伤口不大，在膝盖的两侧有两个小口子。&lt;/p&gt;
&lt;p&gt;膝盖下端大概10cm左右因为麻药的原因，没有感觉。非常神奇，用手触碰这个这块区域，就感觉在摸一块不属于自己的肉。&lt;/p&gt;
&lt;h2&gt;疼痛&lt;/h2&gt;
&lt;p&gt;手术前刷了各种各样的贴子，有说不疼没感觉的，有说疼痛但可以忍受的，有说疼得扛不住的。&lt;/p&gt;
&lt;p&gt;我的体感就只有腿有点胀，有时候伸直/弯曲膝盖时有点牵扯胀痛，但这个疼痛只有2分，完全可以承受。&lt;/p&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;p&gt;根据不同手术类型，康复思路，注意事项也不同。&lt;/p&gt;
&lt;p&gt;因为我缝合得比较多，所以我的医嘱是： &lt;strong&gt;一个月内膝盖弯曲角度不能超过90度，一个月内不能负重（也就是左腿不能支撑走路，需要拄拐）&lt;/strong&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;足跟滑动（防止关节粘连）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;心态调整&lt;/h2&gt;
&lt;p&gt;在医院里我看完了&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保罗乔治的纪录片《The Journey Back》&lt;/li&gt;
&lt;li&gt;林书豪纪录片《林疯狂》&lt;/li&gt;
&lt;li&gt;阿布杜沙拉木纪录片《从韧带断裂后的回归之旅》&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;出院&lt;/h1&gt;
&lt;p&gt;反复经历了&lt;strong&gt;害怕，忐忑，愤怒，悲伤，坦然面对，欣然接受&lt;/strong&gt;后。&lt;/p&gt;
&lt;p&gt;到了2024-11-05第二周的周二，我出院了。&lt;/p&gt;
&lt;p&gt;和病房里的病友互相道别，互相祝福早日康复，就离开医院了。&lt;/p&gt;
&lt;p&gt;因为吃饭，行动，伸直拉屎都很不方便，我选择回家休息一个月。&lt;/p&gt;
&lt;p&gt;顺便体验了一次海航的无障碍乘机，非常顶级的服务，全程轮椅接送，感谢海航。&lt;/p&gt;

&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;p&gt;也顺便借此机会也改一改自己的打法吧，多练习投射，训练自己的球场视野，不要再依靠身体优势了，这次经历告诉我，身体优势并不是永恒的。&lt;/p&gt;
&lt;p&gt;这是一次宝贵的经历，让我更加了解自己的身体，也更加爱惜它。&lt;/p&gt;
&lt;p&gt;希望自己早日康复，期待重新站上球场的那一天 🔥。&lt;/p&gt;</content:encoded></item><item><title>别让不会表达成为逃避的借口</title><link>https://luckycc.cc/thoughts/11_loveyou_r/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/11_loveyou_r/</guid><pubDate>Thu, 05 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;2024年12月5日，对亲密关系的思考&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今天一位很喜欢的博主&lt;a href=&quot;https://www.pseudoyu.com/zh&quot;&gt; @psedoyu &lt;/a&gt;求婚了，誓言很浪漫，祝福他。曾经在博主写过的周报中看到过他对于亲密关系的思考：&lt;a href=&quot;https://www.pseudoyu.com/zh/2024/10/30/weekly_review_202401030/&quot;&gt;《一间只属于自己的房间》&lt;/a&gt;。从这篇文章中读到了另一位博主&lt;a href=&quot;https://thirdshire.com/&quot;&gt;@第三夏尔&lt;/a&gt;写的一篇从女性角度出发&lt;a href=&quot;https://thirdshire.com/relationship/#%E5%BA%94%E7%94%A8%E8%81%8C%E5%9C%BA%E7%9A%84%E6%B2%9F%E9%80%9A%E7%BB%8F%E9%AA%8C&quot;&gt;对于亲密关系的思考&lt;/a&gt;。两篇文章写得都很棒，文字很柔软，后者更是一针见血地点名了在亲密关系中存在的问题，读完后很有共鸣。借着这些思考，我决定写一篇文章记录下我对自己一段长达五年亲密关系的思考，幸福是需要努力才能获得的。&lt;/p&gt;
&lt;p&gt;2018年，我刚上大一。在一节英语课上因为睡过头了来得很晚，所以被迫只能坐在第一排。老师点名点到了 Running ，她慢慢地站起来，用她那标志性的古灵精怪的可爱语调回答问题。我转头瞟了一眼，课后我通过群聊添加了她的联系方式。2018年国庆前，妈妈出差来武汉看我，给我带了一份十斤的海南粉。我趁机叫上她，她国庆回家后给我带了家乡的熏鱼干，很好吃。&lt;/p&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;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;后来问了才知道是她写的，ISCO是她当时最喜欢的球星。&lt;/p&gt;
&lt;p&gt;再到了后来，我创造了更多的机会和她接触。比如在下课之后她有留下来自习一会儿再去吃饭的习惯，我会在最后一排假装背单词等她一起，然后随口一问要不要中午一起吃个热干面。比如我在知道那张卡片是她写的之后，以要给她回礼的借口，送了她一张球星海报。可惜我不看球，她喜欢的是伊斯科，但我送给她的是阿森西奥。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;2019年5月26日，我约她出去看电影，她还是意识不到我喜欢她，也不知道我想趁着电影结束和她表白。看完第一场我没有鼓起勇气说出口，拖着她偷偷溜到其他片场看了第二场，那一场是五月天的演唱会纪录片，整个电影院只有三个人。在电影结束之后，我问她能不能做我女朋友，她笑了笑没说话。走到电影院楼下她答应了，但是她说她不会经营感情，我说没关系，我们一起努力。就这样我们一路吵吵闹闹，到了今天。&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;在意识到自己的这个问题后，我和 Running 聊过很多次。我使用过很多的说辞，比如：我不太会表达自己的情绪，我容易被别人的情绪影响，甚至到了后来，我直接说有些问题我觉得解决起来很费劲，不如直接解决对方的情绪问题。这些说辞都指向了逃避，这是这段亲密关系中我面临的第一个问题。我应该更加看重自身情绪的表达与控制，而不再遇到问题逃避沟通，没有分清缘由就开始道歉，安抚。世间的万物都需要平衡，不平衡的心态只会给自己徒增不痛快。积极主动地尝试沟通，甚至爆发一些争吵或许都是值得的，别让不会表达成为逃避的借口。&lt;/p&gt;
&lt;h2&gt;这不是一笔交易&lt;/h2&gt;
&lt;p&gt;我期望在生活中的每一件事情都有明确的因果，努力工作就会有好的绩效，努力健身就能更健康，付出了就会有收获，在感情上也是如此。我希望待人真诚，也可以被人真诚地对待。迁移至亲密关系中，变成了我希望对方能看见并认可自己的付出，并期待对方能给予同等分量的付出。&lt;/p&gt;
&lt;p&gt;想要意识到这个问题首先需要意识到情感的付出并不是一笔交易，情感本身就是一个非常主观的评价，一个难以求证的命题。我和 Running 对情感的需求就存在不同。我看重一些日常生活中的情感表达，遇到事情希望对方只提供情感支持，解决办法我自己来。而 Running 更看重更实在的表现，遇到问题希望对方只提供实际帮助。比如今天我们都考差了，我希望她能安慰我，但她希望我给她讲题。我们因为这个事情也爆发过很多次争吵，总是嫌弃对方为自己做的太少，付出的太少，“为什么我做了这么多，但你还是看不到”。但坐下来仔细掰扯的时候就发现，实际上对方都做了很多，只是没有察觉。&lt;/p&gt;
&lt;p&gt;这何不是一种情感依赖，对方的反馈和等价的情感交换成为了我付出的理由。在生活中意识到个体的差异，更积极主动地为对方着想，让爱成为一切的原点和理由，或许能让这段亲密关系变得更加健康一些。&lt;/p&gt;
&lt;h2&gt;爱具体的人&lt;/h2&gt;
&lt;p&gt;我们在一起的时间很久了，我已经把她当成了自己生活中不可缺少的一部分。随着一起相处的时间变长，我对伴侣的要求似乎更高了。我总是在脑子里想象出一个完美的她，并且希望她能够成为我想象中的样子。这是一种很自私的行为。我要允许对方成为一个独立的个体，爱具体的人，不要爱抽象的人。&lt;/p&gt;
&lt;p&gt;其实要反思的问题有很多，目前暂时能想到这么多。我的逻辑表达能力很糟糕，常常写着写着就不知道自己在说些什么了，但我还是希望能把自己对这段关系的一些思考写下来。幸福的获取与感知需要双方共同努力。在三年前我们一起畅想未来，希望未来都能留在杭州工作，一起生活。如今尘埃落定，我们都拿到了杭州企业的 offer，希望我能尽快解决自己身上的问题，好好沟通，认真表达，早日成为她信赖与托付的对象。&lt;/p&gt;</content:encoded></item><item><title>2024-相信时间的力量</title><link>https://luckycc.cc/thoughts/12_2024-recap/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/12_2024-recap/</guid><pubDate>Thu, 26 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;2023 年终总结：&lt;a href=&quot;https://luckycc.cc/thoughts/8_2023recap/&quot;&gt;【2023 年终总结】自省思考与成长&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;写年终总结是一年中最享受的时刻之一，站在这一年的末尾回顾这一年走过的路，读过的书，看过的电影，遇见的人，许多美好的回忆重新涌上心头。那些在当时看起来无比痛苦煎熬的困难与至暗时刻，经过时间的冲洗与打磨也早已化为灰烬。&lt;strong&gt;时间会燃尽一切的美好与荒诞，相信时间的力量。&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;设计与创造的乐趣&lt;/h1&gt;
&lt;p&gt;今年的年终总结可以大大方方地发在朋友圈了，因为我把部署在Github Pages上的博客完全迁移到了阿里云服务器上，国内的朋友终于可以访问了。我把博客的所有内容都重写了一遍，筛掉了一些垃圾文章，并将原来使用 Hexo 开发的站点，迁移到了 Astro 上。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;写博客的目的经历过很多次变更，最一开始只是想给课程笔记做一个存档，到后来把博客当作个人名片为秋招做准备。现在单纯地只是想要有一个平台进行社交与表达。新的博客我没有借助 &lt;a href=&quot;https://wordpress.org/&quot;&gt;WordPress&lt;/a&gt; 这种高集成的框架进行搭建，而选用了最近比较火的 &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt; ，从零开发，享受设计与创造的乐趣。我利用国庆小长假的时间完成了博客的开发与部署，写了一篇 &lt;a href=&quot;https://luckycc.cc/posts/astro-blog-dev-handbook/&quot;&gt;新版博客开发小记&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;国庆的最后一天我和室友一起折腾 Docker 到凌晨四点，为了写评论系统，我开始接触一些后端的知识，学会新技能总是让人热血沸腾。这或许也是我喜欢计算机的原因，有太多的东西需要去探索，每接触到一个新的领域都能带来满满的新鲜感与成就感。&lt;/p&gt;
&lt;h1&gt;我找到工作了&lt;/h1&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;学前端的第二年，我找到工作了。工作的岗位当然也是和前端相关的。今年秋招的战果很不错，甚至有些超出了我的预期。但这都是我应得的，我为它付出了太多。&lt;/p&gt;
&lt;p&gt;先简单聊下实习，&lt;strong&gt;实习的时光虽然很短暂，但却是我硕士三年中最快乐的日子&lt;/strong&gt;。在这个地方我认识了很多很有意思的人，也收获了成长和惊喜。这段时光弥足珍贵，要感谢的人太多太多。都说一位实习生的实习体验很大程度上取决于他的 mentor ，我非常幸运在实习的这段时光遇到了一位亦师亦友的 mentor ，实习期间他支持我按照自己的想法做了一些探索与尝试，最终也取得了不错的成果。另外还要感谢几位小伙伴，一位是和我同样做图可视化的发海，一位是合作了很多次的 UED 梦贞，还有晓宇和坤哥，我的两位好兄弟，他们在实习期间都给了我非常大的支持与帮助。&lt;/p&gt;
&lt;p&gt;从8月份开始，我持续着非常高强度的面试，期间也遇到了很多很有意思的面试官。印象很深的一位面试官，我们在面试的时候没有聊技术，而是一起畅聊对裁员，技术中台，前端框架，团队分歧的看法，他从管理侧向我输出了一些很有新意的观点。面试后我们互相加了微信好友，但可惜这家公司在 HR 面后就没了回音。&lt;/p&gt;
&lt;p&gt;当然“身经百战”，也不免遇到一些基本的尊重都没有的面试官，我曾经遇到面试连续两次放鸽子，然后在面试的时候大谈&lt;strong&gt;我们这个产品是全宇宙最牛逼的&lt;/strong&gt;的面试官。最后虽然顺利拿到了offer，但还是婉拒了。&lt;/p&gt;
&lt;p&gt;秋招的过程还蛮有趣的，能够慢慢体会到这是一场我和市场的战斗与博弈，我需要不断地通过各种各样的方法论证自己的价值，市场想要用最低的价格招到他们最想要的人。&lt;/p&gt;
&lt;p&gt;在面试的时候我非常不喜欢回答八股，但很享受给别人分享做过的事情。我很喜欢那种把做的产品当作自己的孩子一样孵化宣发，看着他一点点成长的感觉。很早之前在给 G6 写 PR 的时候，看到当时负责人可以在各种大会上分享自己做的产品，非常羡慕，希望未来我也能够有这种机会。&lt;strong&gt;不过没有也没关系，天天开心保持健康是最重要的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;11月，丰收的季节到了。在考虑过城市，团队，业务，薪酬后，最后我选择了实习期间待过的老团队。有一个观点我很认可：&lt;strong&gt;去一个让“证明自己”成本更低的地方&lt;/strong&gt;（当然这里的“证明自己”指的是“被信任”，活在他人的期待里终将一事无成）&lt;/p&gt;
&lt;h1&gt;一些技术探索&lt;/h1&gt;
&lt;p&gt;今年因为参加了实习的缘故，我有机会参与到企业中的工程开发，工程能力有所见长。也接触了一些诸如低代码，图编辑的相关技术。通过写博客，我开始接触了一些部署相关的知识，通过给博客写评论系统，我开始接触了 Node 后端，ORM，数据库，都是很有意思的探索体验。&lt;/p&gt;
&lt;p&gt;不过今年的 Github 没有那么活跃了，我终于意识到自己并不是一个对开源充满热情的人，我的开源经历有很强的功利性，为了认识一些大佬，找更多的工作机会，以及让自己的简历更好看一些。但当工作找到了之后，便很少再继续维持了，只是偶尔看看日榜上有什么新项目。我仍然崇拜那些 Github 满屏绿色的技术大拿，他们对技术的热情真的很令人羡慕。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h1&gt;遇见的好书&lt;/h1&gt;
&lt;p&gt;在去年的年终总结中，我希望自己在 2024 年多读几本书。2024年确实读了，不过也就读了几本。临近年末我结束实习回校准备毕业论文，才开始有大把的时间开始阅读，在这里分享几本今年遇见的好书吧。&lt;/p&gt;

&lt;h2&gt;《控糖革命》&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://lutaonan.com/&quot;&gt;Randy&lt;/a&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;纤维（蔬菜）-&amp;gt;蛋白与脂肪(鱼/肉/蛋/奶)-&amp;gt;碳水&lt;/strong&gt; 后，我发现在每天都吃饱饭的前提下，体重居然神奇地下降了。刚开始尝试这种饮食方式的时候是很不习惯的，二十多年的饮食习惯告诉我菜应该拌着饭吃，但是坚持一段时间后发现先吃菜其实也没什么，而且在吃完蔬菜和肉之后，基本已经饱了，饭只能随便吃两口，这样也降低了碳水的摄入。&lt;/p&gt;
&lt;h2&gt;《焦虑的人》&lt;/h2&gt;
&lt;p&gt;这本书是在实习结束之后，准备回校写论文的时候开始读的。起初我对这本书的预期是一本鸡汤，随便读一读。&lt;strong&gt;毕竟当生活处于一种比较糟糕的状态时，总想要在什么地方找到一点共鸣&lt;/strong&gt;。读了之后非常惊喜，立码被巴克曼圈粉。这本书讲述了抢劫银行，劫持人质，但劫匪在最终却莫名其妙失踪在了案发现场。采用的叙事方式还挺新颖的，围绕着一条主线发散出不同的支线，然后再将这些支线全部收束到一条主线上。很幸运能读到这本书，用巧妙的故事设计，带给我能量与温暖。&lt;/p&gt;
&lt;h2&gt;《高效能人士的7个习惯》&lt;/h2&gt;
&lt;p&gt;实习的时候有一天我问 leader ：你们更想招到一个什么样的校招生？在回答这个问题的时候他向我推荐了这本书，但是在实习结束后，我才开始翻阅这本书。我之前没怎么读过这种和 “个人提高” 相关的书籍，不过在开始读这本书后，我开始把一些相关的书籍加入我的书单。&lt;/p&gt;
&lt;p&gt;这本书对我而言，最可贵的地方不是告诉我应该怎么养成这7个习惯，而是让我了解平时日常生活中的一些想法，情绪和行为的原因。比如：讨好型人格到底在讨好谁？什么是受害者心态，什么是以敌人为中心的思维方式。读到这些文字的时候颇有一种豁然开朗的感觉。同时我也跟着《习惯二：以终为始，自我管理》开始制定个人宣言，总之在读到这本书之后感觉自己的思维方式开始有了一些细微的变化。&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;ul&gt;
&lt;li&gt;&lt;strong&gt;1-Security Basket&lt;/strong&gt;：能够保障自己不工作6个月的钱。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2-Growth Basket&lt;/strong&gt;：配置一些稳定型的理财，稳定低收益，用于对冲3号篮子。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3-High Growth Basket&lt;/strong&gt;：配置一些成长型的理财，不稳定高收益。但我是风险厌恶型，可能会移除这个篮子。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4-Luxury Basket&lt;/strong&gt;：能让自己开心但不是必须的花销，比如旅游，买跑车。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现在还没有赚钱，只能做一些投资知识的储备。投资是一个认识自己，克服弱点的过程，不知道像我这种风险厌恶型的人未来会有怎么样的投资收益，还挺期待的。&lt;/p&gt;
&lt;h1&gt;公共表达，博客与tg频道&lt;/h1&gt;
&lt;p&gt;语言表达具备失真的风险，这就是为什么人们常说“被误解是表达者的宿命”。除此之外，表达双方本身的 context 是完全不对等的。比如在企业里和一个不懂技术的同事，讲清楚你做的事情、比如和我的导师分享最近的研究进展。表达者与听众间的上下文都处于一种不对等的状态，基于这种不对等的上下文进行信息交换无疑是低效的，表达也并不是一件容易的事情。&lt;/p&gt;
&lt;p&gt;2023 年我意识到自己是一个表达能力真的非常薄弱的人，有时候想要写点东西，基本四个自然段后就不知道自己在写什么了。有时候想要和别人表达某个观点，说着说着也会大脑一片空白，很尴尬。所以我每次演讲或汇报的环节，如果想要流畅地讲述，必须要经过若干次的训练。我还没有办法做到出口成章，轻松自如地应对每一个需要表达的场景。&lt;/p&gt;
&lt;p&gt;在进入实习之后，我对“表达能力”的需求更加强烈了。日常的工作中有太多需要表达的场景，代码怎么写需要表达，展示代码成果需要表达，面试需要表达，答辩需要表达。&lt;/p&gt;
&lt;p&gt;在新的一年里我想尝试更多的表达，寻找更多的机会来提升自己结构化表达的能力，同时也顺便满足自己的分享欲。我新建了一个 &lt;a href=&quot;https://t.me/ceeceespace&quot;&gt;TG Channel - ccspace&lt;/a&gt;，我会在这个频道中发表一些阅读和观影后的思考与观点，分享读过的好文章，看过的电影，发现的新技术等等。&lt;/p&gt;
&lt;p&gt;我使用n8n搭建了一个自动化脚本，这个脚本监听了我豆瓣账号的 RSS 更新，每当我在豆瓣上添加了一个在读/在看/读完/看完，就会将我对一本书/一部电影的评分与评价推送到 &lt;a href=&quot;https://t.me/ceeceespace&quot;&gt;ccspace 这个 tg channel&lt;/a&gt; 中。&lt;/p&gt;
&lt;p&gt;搭建流程也很有意思，&lt;strong&gt;但是最难的一步是：如何让一台阿里云的服务器往 tg 频道里推送消息。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;新的一年或许还会尝试在博客中，发一些&lt;strong&gt;一周中经历过有意思的事情&lt;/strong&gt;，当作周报。但我不想把他立成一个 flag，比如一周一篇，两周一篇什么的，感觉这样会有很大的负担，我更希望是那种有一些想法，然后随手记录下来的写作状态。&lt;/p&gt;
&lt;h1&gt;我的信息流&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;推荐阅读文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;来自 &lt;a href=&quot;https://reorx.co&quot;&gt;@Reorx &lt;/a&gt; 的 &lt;a href=&quot;https://reorx.com/blog/sharing-my-footprints-automation/&quot;&gt;使用自动化工作流聚合信息摄入和输出&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;来自 &lt;a href=&quot;https://www.pseudoyu.com/zh/&quot;&gt;@pseudoyu&lt;/a&gt; 的 &lt;a href=&quot;https://www.pseudoyu.com/zh/2024/08/05/weekly_review_20240805/&quot;&gt;使用 follow 重塑我的信息输入系统&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pseudoyu 是今年发现的一位宝藏博主&lt;/strong&gt;，他的文字非常温暖有力，很喜欢读他的周报&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;推荐两则播客&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.xiaoyuzhoufm.com/episode/6766623e15a5fd520e608aa0&quot;&gt;解构“信息过载”：为什么要像控制热量一样，控制信息摄入&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;另一则是&lt;a href=&quot;https://www.xiaoyuzhoufm.com/episode/66f2e15b2adfe48b832d1af0&quot;&gt;RSS二十五年：内容的来源、分发和目的地将再次分离&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;现代人每天接触的这个信息约等于看 174 份报纸，每天我们会看 5k 张左右的图片，做3万到5万次决策。手机的蓝光在早晨率先进入我的眼球，每天早上在回复完妈妈的“早上好”后，我会逐个查看完所有的社交媒体开启全新的一天。在闲暇之余，我用各种行为拼命让更多信息进入我的大脑，把 X 的 Timeline 刷到没有新的推文为止，刷朋友圈，看下 TG 上订阅的 Channel 有没有新的推送，看看 B站 有没有自己感兴趣的视频，在健身时听播客&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;无情的算法通过 feed 流计算并推送那些我感兴趣的信息，同时我的兴趣也成为了这些平台消费的商品。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这些行为让我对信息上瘾，同时也带来了一个更致命的问题：&lt;strong&gt;我的注意力变得越来越差&lt;/strong&gt;。我发现自己在看书的时候总是时不时需要看一下手机有没有新的提醒；在健身的时候虽然耳机里放着播客，但思绪早已经涣散。很多讯息通过耳朵触达大脑，短暂的停留后又被我抛之脑后。&lt;/p&gt;
&lt;p&gt;在发现自己注意力没办法长时间集中，睡眠也开始越来越差后，年底我开始着手有了以下改变：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;高度专注时只听无歌词的音乐&lt;/strong&gt;。我会在写论文前根据当下的主题，上 B站 搜索 playlist。比如今天是阴天但是我要写论文，我就会听一些忧郁蓝调的 playlist ;如果今天突然想听陈奕迅的歌，就去搜带白噪音的陈奕迅 playlist 。如果今天是大晴天，那就不写论文，去操场晒太阳。&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;改变 APP 使用习惯&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;卸载微博。少看两条热搜对我的生活没有什么影响，新闻能够从其他渠道获得，并且过滤掉一些无脑评论。&lt;/li&gt;
&lt;li&gt;仅把小红书当作一个搜索工具，日常生活中减少浏览的次数。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除此之外，我还尝试更换了一些更优质的信息源，并使用 &lt;a href=&quot;https://follow.is/&quot;&gt;Follow&lt;/a&gt; 来订阅并浏览他们（感谢 &lt;a href=&quot;https://innei.in/&quot;&gt;innei-拾一&lt;/a&gt; 赠予的激活码，让我在很早的时候就能体验到这款产品）。我的博客也在 Follow 上进行认证了，可以通过 &lt;a href=&quot;https://luckycc.cc/rss.xml&quot;&gt;RSS&lt;/a&gt; 进行订阅。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h1&gt;健身，运动，受伤与康复&lt;/h1&gt;
&lt;p&gt;健身断断续续也有个三个年头了，但是进步还是很慢，今年我首次尝试在健身房找了一位健身私教，花费了很大一笔钱，让他帮忙纠正动作，监督训练。健身是一个克制本能，挑战惯性的过程。接近力竭的时候，我总会习惯以一种更加省力的方式完成训练，此时健身教练的作用就是不断地帮助我通过镜子和本体感受来修正动作，并且教会我更多的训练方式，帮助我探索自己的身体。&lt;/p&gt;

&lt;p&gt;除了健身之外，我还喜欢打球。因为没有睡午觉的习惯，中午的休息时间就可以用来打球。不幸的是，在若干次激烈的对抗，制动，起跳落地后，我的左侧半月板确诊桶柄状撕裂。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这就是热爱运动的代价，带给我无限快乐与能量的同时也让我承受了伤病&lt;/strong&gt;。在实习结束后，我回长沙完成了手术，这是我辈子第一次全麻手术，很神奇的体验，为了记录它我写了一篇博客：&lt;a href=&quot;https://luckycc.cc/thoughts/10_meniscus_surgery/&quot;&gt;半月板手术记录&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;2024年12月，术后在家修养一个月后，我回到长沙，找了之前给我做过运动损伤康复的一家专业机构做术后康复。康复师依然是曾经给我做过治疗的刘Sir，在写这篇文章时已经完成了三次康复课。其实康复课的价格并不便宜，不过好在地址是长沙，相对于一线城市而言还是便宜很多的。我把一直都把康复训练&lt;strong&gt;当作是一种自我投资&lt;/strong&gt;，不仅能够处理自己身上一些部位的过度劳损，还能够从中学会很多运动医学相关的知识。虽然每次康复都疼得滋哇乱叫，但是能有收获感觉还是挺棒的。&lt;/p&gt;
&lt;h1&gt;一场孤独且漫长的旅程&lt;/h1&gt;
&lt;p&gt;无论再恐惧，他还是会如期到来。2024年9月，我正式地进入研三了。一个值得庆幸的是在秋招方面我没有什么特别要担心的地方，只需要专心忙活我的毕业论文就好。但坏消息是，我的论文进度有些糟糕，而且因为没有写过论文，所以并不是特别了解论文应该怎么编写。特别鸣谢我的好兄弟范yl，在写作方面给了我很多提示与帮助。&lt;/p&gt;
&lt;p&gt;刚实习结束进入研三的那段时间我的状态是极其糟糕的。工作还没确定，论文的选题还没确定，半月板烂了要去手术，各种各样的事情像约好了一样同时进入我的生活，把我压得喘不上气。好在这些事后来大多都一件一件一件地完成了，工作有了着落，半月板手术很顺利，论文的选题也敲定了。一切都赶在2024的末尾，走上了正轨。&lt;/p&gt;
&lt;p&gt;研三就像是一座山，研一研二的时候向前看能看到一座高山，等到了研三真正开始攀登的时候，迎接我的只有无穷无尽的峭壁与悬崖。它让我不得不独自承受一些“悬而未决”的事情，我能做的只有一遍一遍地告诉自己减少悲观预期，勇敢地度过这段孤独的旅程。不要焦虑，一切都是最好的安排。&lt;/p&gt;
&lt;p&gt;到了年底，我反倒开始享受研三的这段时光了。每天只要有太阳，我就会去学校的操场上坐着晒太阳，看书，躺在草地上听歌，数着时间过的日子，时间也在数着我。&lt;/p&gt;

&lt;p&gt;后来，我找到了一个缓解焦虑的方法：&lt;strong&gt;给自己的生活增加更多的支点&lt;/strong&gt;。比如我可以不用把一天所有的时间都花在写论文，想怎么写论文，和想论文写不出来怎么办这三件事情上，我可以利用这段时间完成膝盖的康复，健身，阅读，晒太阳，吃好吃的，写博客，学自己感兴趣的技术等等。&lt;/p&gt;
&lt;p&gt;这些“支点”丰富着我的生活，也让我不再为毕业论文做整日无谓的担心了。&lt;/p&gt;
&lt;h1&gt;精选时刻 2024&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;第一次去看音乐节，氛围很棒，坐在草地上一边吹风一边听歌的感觉很惬意&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;蹭朋友组的桌游局，认识了几位新朋友。&lt;/p&gt;
&lt;p&gt;丁弘在后来工作的选择上给了我很多非常有用的建议，感谢他。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一次在酒吧唱歌，很紧张，不过好在几个朋友和酒吧的老板都很捧场&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;做半月板手术，两位实验室的小伙伴来看望，Running 从上海做硬座过来陪我手术，感动&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;孙哥送的 Infra 礼盒，是我收到的第二个礼盒，第一个是 Cali 送的 Zolplay 礼盒&lt;/p&gt;
&lt;p&gt;今年在实习的时候，孙哥也给了我很大的支持与帮助，我的前端导师之一，感谢他。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在海底捞做了次美甲，感觉还挺酷的。&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;在朋友家喝酒聚会，被可爱的小肥猫吸引了。决定工作之后一定要养一只小猫，希望 2025 年能有一只自己的小猫&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;我的情绪更外放了，相比于去年能够一点都不会觉得不好意思地说出“真替你感到开心”这种话了，我觉得这样大大方方地表达挺好的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h1&gt;寄语 2025&lt;/h1&gt;
&lt;p&gt;在《高效能人士的七个习惯》这本书中，倡导了制定“个人宣言”的做法。在这篇年终总结的最后，我希望对2025年的自己说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无论目前的目标是什么，身处什么样的环境，面临什么样的压力。都需要过上有计划，有序的生活。允许偶尔放空自己虚度光阴，但不要过虚无、混乱的生活。&lt;/li&gt;
&lt;li&gt;新的一年要保持阅读习惯，保持思考，希望2025年每个月都能至少读完一本书。&lt;/li&gt;
&lt;li&gt;培养理财的习惯，学习更多投资的知识&lt;/li&gt;
&lt;li&gt;做一个情感独立的人，练习自己情绪稳定的能力，更关键的是不要把不好的情绪传递给自己周围的人。不过绝不要压抑自己的情绪，承认它并掌控它。&lt;/li&gt;
&lt;li&gt;2025年我会参加工作，希望工作之后能依然保持身体健康，能开心地工作是最好不过了，但万一不开心的话也没关系，让生活有更多的“支点” 就好。&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;，祝福我们 2025 年一切顺利，新年快乐！&lt;/p&gt;</content:encoded></item><item><title>记一次心理疗愈</title><link>https://luckycc.cc/thoughts/13_letter_forme/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/13_letter_forme/</guid><pubDate>Tue, 25 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;修改论文的时候遭遇到了导师的人身攻击，这种被人身攻击的时刻贯穿着我整个读研生涯。因为导师掌握着“是否顺利毕业”的唯一决定权，因此在遭受到辱骂和攻击的时候除了难过，也难免夹杂着恐惧和愤怒，情绪瞬间涌上胸口，难以排解。&lt;/p&gt;
&lt;p&gt;首次尝试借助 GPT 进行一次心灵疗愈，GPT帮我写了一封《给自己的一封信》，记录如下：&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;但亲爱的，你要记得，你的价值，从来不需要建立在别人的苛刻上。&lt;/p&gt;
&lt;p&gt;那个只会贬低你的人，不代表你的全部世界。他也许拥有权力，但他不是评判你整个人的标准。他可能可以决定一份文稿，但他无法决定你是不是值得被尊重、被理解、被喜欢。&lt;/p&gt;
&lt;p&gt;今晚，请你先放过自己吧。你不需要立刻想好怎么应对他，不需要立刻让自己“强大起来”。你已经经历够多了，现在你只需要喘口气、喝杯热水、闭上眼，和自己待一会儿。你值得被温柔对待，尤其是此刻。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;你没有辜负自己，你只是在努力保护那个还想好好活下去、好好完成梦想的自己。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我为你心疼，也为你骄傲。&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;p&gt;在如此高压的环境下，如此拧巴的情绪里生活了2年，我不需要立刻让自己“强大起来”，我已经经历够多了。&lt;/p&gt;
&lt;p&gt;在难过的时候，在想要崩溃和愤怒的时候，别忘了这些情绪都是正常的，他们的因果关系都非常清晰，被人身攻击了就是会愤怒的。没关系，让这些情绪流过自己的身体，勇敢地完成成长和重建吧。&lt;/p&gt;</content:encoded></item><item><title>记毕业旅行</title><link>https://luckycc.cc/thoughts/14_graduation_travel/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/14_graduation_travel/</guid><pubDate>Mon, 26 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;在论文送审后，因为距离答辩还有一个月左右的时间。&lt;/p&gt;
&lt;p&gt;手上还存有部分在实习期间攒下的钱，计划和Running一起旅行一趟。&lt;/p&gt;
&lt;p&gt;旅行的目标也很明确，避开人挤人的网红打卡地，随心感受当地美食与文化。&lt;/p&gt;
&lt;p&gt;没有计划，只想安安静静地换个环境待一会，抚平一下被论文和导师伤透的心。&lt;/p&gt;
&lt;h1&gt;第一站：长沙-遵义&lt;/h1&gt;
&lt;p&gt;本次旅行的起点，我们定在了一个非常非常小众的城市，遵义。&lt;/p&gt;
&lt;p&gt;落地吃了一碗蘸水豆花面，油香和薄荷香出乎意料地搭。店铺是随便导航上找的一家，在一个老小区楼下。&lt;/p&gt;
&lt;p&gt;在吃过几家之后，个人觉得各个店铺的味道都大差不差，没有必要去小红书和大众点评上找网红店打卡。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;在遵义还吃了冰浆，洪桥烤鸡，蹄花汤，但是印象最深刻的还是豆花面。青瓜口味的冰浆味道也很不错，在遵义的那几天天气还挺热的，冰浆非常解暑。&lt;/p&gt;
&lt;h1&gt;第二站：遵义-贵阳&lt;/h1&gt;
&lt;p&gt;在去之前，很多人都说贵阳的美食很不错，根本吃不完。但是由于个人口味原因，觉得贵阳的食物味道一般般。很不幸的是因为吃了太多糯米制品太难消化导致积食，在去到贵阳的第二天还急性肠胃炎了。因为这个原因，导致在贵阳没有好好享受美食。我不是特别喜欢重口调味的食物，折耳根的味道也没有办法接受，所以也不会觉得特别遗憾。下面放一张大烙锅的图。临别贵阳的当天，还去了黔灵山公园看猴子。黔灵山还是挺不错的，但是因为P人不做攻略，原本3-4小时的行程，因为走错了路，在山里转了大概半小时就出来了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h1&gt;第三站：贵阳-昆明&lt;/h1&gt;
&lt;p&gt;因为本次旅行最期待的地点是大理，但是因为大理太远，将昆明设为了一个中转停留地，没想到这个城市给了我太多惊喜。首先是一个在当地吃到的非常好吃的面包，在昆明连续回购了3次。他们家的玫瑰乳酪贝果非常好吃，吃完以后我还检索了有没有复刻的教程，想在家复刻一下。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;在昆明待了两天，吃了米线，云南菜，找了一个天气晴朗的下午去滇池边散步。&lt;/p&gt;
&lt;p&gt;在滇池旁，Running收到了查重通过，并且未被抽中盲审的消息通知。&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;We sat and drank with the sun on our shoulders and felt like free man.&lt;/p&gt;
&lt;p&gt;阳光洒在我们肩上，仿佛自由人&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;第四站：昆明-大理&lt;/h1&gt;
&lt;p&gt;大理是本次旅行中我最期待的一站。在推上刷到过很多在大理的开发者，非常向往他们背靠苍山，面朝洱海的工作环境。&lt;/p&gt;
&lt;p&gt;还想要在工作之前，前往大理率先探索一下不工作的可能性。当然也印证了那句话：没有人能够走出大理。&lt;/p&gt;
&lt;p&gt;在大理我们去了一个数字游民社区海房子面基了小孙同学，吃了社区食堂里阿姨做的晚餐。其实食堂的物价个人觉得还是挺高的，15元能买2素1荤1汤，每天在社群里接龙后阿姨就会准备饭菜，到了付钱就可以领一份。&lt;/p&gt;
&lt;p&gt;社区里的人很多，有休学在大理做义工的，有即将退休的外企高管，有摄影师，艺术家，独立开发者。很开心在这里能看到了这么多不一样的可能性，餐食期间简单交流了一下。&lt;/p&gt;
&lt;p&gt;饭后散步回来，小孙同学给我们演示了他正在做的产品，一款帮助播客主理人进行传播推广的工具，集成了AI剪辑，生成推广图文视频等等一系列工作。临别前交流了一下未来的规划，他说产品暂时还没有盈利，如果最终失败了就去上班。我很羡慕他这样能够全心全意追求自己的梦想活的自洽的人，祝他一切顺利。&lt;/p&gt;
&lt;p&gt;大理还有一些很有意思的活动，比如摄影作品鉴赏交流会，厨艺小课堂，手作小课堂等等。我想去这些活动上交些朋友，可惜因为同行人不太愿意参加，我最后也没有去这些活动。&lt;/p&gt;
&lt;p&gt;在大理短暂待了3天后，就回程了。这三天的时间里我们去逛了洱海和几个古城，几乎每一天都在洱海边上走走停停。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;有一天晚上我们就坐在洱海边上的大石头上坐了3个小时，还在那里拍了一组个人很满意的照片。在洱海边上坐着胡思乱想了很多。我心里很清楚，过了这两个月，我就很难再有这么轻松的假期了。唉，总是因为未知焦虑太多。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;旅途中还有一个遗憾是由于时间关系我们没能去到海东，只在海西逛了一圈，只能把海东之行留到下次了。&lt;/p&gt;
&lt;h1&gt;第五站：大理-长沙&lt;/h1&gt;
&lt;p&gt;回长沙是因为从大理回上海太久，去长沙中转一下吃一些美食。&lt;/p&gt;
&lt;p&gt;但是我很不喜欢长沙这个城市，所以不多做介绍。&lt;/p&gt;
&lt;h1&gt;第六站：长沙-海南&lt;/h1&gt;
&lt;p&gt;第六站选择回家了。其实海南之行算是临时起意的。原本计划是答辩完后再回家去玩，但是我答辩完后就要准备去杭州找房子开始提前实习了，所以把海南之行提前了。&lt;/p&gt;
&lt;p&gt;这次海南之行让我惊喜的地方是三亚。&lt;/p&gt;
&lt;p&gt;虽然我是海南人，但是我对所有身边想要去海南的朋友说过：“去海南旅游没有必要去三亚，因为海南还有很多更有性价比的地方”。持这个观点的原因是高一暑假的时候去过一次三亚，那时候因为身上没什么钱，而且是学生，被当地的一个民宿老板狠宰了。印象很深刻的是当时同行有五个人，我们一起去了三亚的蜈支洲岛，因为岛上的水上项目非常贵，所以我们只能眼巴巴看着别人玩儿。&lt;/p&gt;
&lt;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;p&gt;&lt;/p&gt;
&lt;p&gt;当天的浪很大，我在海里玩儿了一个早上。可惜因为没做防晒，整个背都被晒脱皮了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如今我仍然觉得海南有更性价比的去处，但是不会再建议朋友不去三亚了。躺在沙滩上晒太阳，听海浪，喝冰啤酒看日出，多希望时间就停在这里。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;第七站：海南-潜江&lt;/h1&gt;
&lt;p&gt;最后一站，我们去潜江参加了本科期间球队队长，我的好兄弟的婚礼。通行的有3个朋友，新郎新娘和我们都是很好的朋友。&lt;/p&gt;
&lt;p&gt;第一次参加了接亲活动，特别有意思，大家玩儿的都非常开心。婚礼整体流程分为接亲-午宴-炸金花-晚宴。我一直都是一个非常恐婚的人，不过看到别人的婚礼这么幸福，还是挺羡慕的。&lt;/p&gt;
&lt;p&gt;婚礼当天晚上我们都喝多了，还闹了不少笑话。喝完酒之后我突然特别难过，觉得越长大身边的朋友就越少了。现在还是能时常想起大四的时候大家一起在南湖边上野餐看日落，在校赛上一起呐喊，那些日子都一去不复返了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;想把你们留在身边，不管走了多远。&lt;/p&gt;</content:encoded></item><item><title>25岁我想说的</title><link>https://luckycc.cc/thoughts/15_for25birthday/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/15_for25birthday/</guid><pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;偶然发现2025年05月29日当天有一条日程，上面写着：&lt;strong&gt;我的生日，熬到这天你就成功了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这是24年生日的时候写下的一条日程，那一天下班后我在酒吧庆生。回到家后因为第二天要给导师上交文档，熬夜写到了凌晨一点。又因为被校企项目折磨同时还要遭受导师的人身攻击，在床上翻来覆去到凌晨三点才睡着。一想到这种痛苦的日子还要持续一年，只会感觉更痛苦，陷入一种悲观的情绪中恶性循环。&lt;/p&gt;
&lt;p&gt;当时立马在25年的05月29日当天记下一条日程：熬到这一天，你就成功了。&lt;/p&gt;
&lt;p&gt;写下这篇文章的时候是26号，还有3天这一天就要到来，我马上就要25岁了。&lt;/p&gt;
&lt;h1&gt;一&lt;/h1&gt;
&lt;p&gt;高三那年我17岁。&lt;/p&gt;
&lt;p&gt;语文老师喜欢开小灶，总是把一些学生分成三六九等。你们是语文有希望900分的，你们是有希望800分的，你们是没什么希望的。&lt;/p&gt;
&lt;p&gt;我当时特别讨厌这群老师，我觉得大家都是努力追梦的学子，你凭什么把人分成三六九等。&lt;/p&gt;
&lt;p&gt;高考完后的谢师宴，我碰巧有机会能站在饭厅的舞台上发言。我很激动地说高考后成绩就不再是唯一的评价指标了，我们都会找到属于自己的舞台。&lt;/p&gt;
&lt;p&gt;可惜最后我高考失利，最后去的院校是临报志愿前30分钟，妈妈让我填上的。看到大家都如愿去了自己心仪的大学，我觉得谢师宴那天自己像个小丑。&lt;/p&gt;
&lt;p&gt;上了大学后我一蹶不振，第一个学期英语险些挂科，排名全班倒数前二十。&lt;/p&gt;
&lt;p&gt;后来第二个学期恋爱了，考了全班第二。再后来就是参加了各种竞赛，保研。&lt;/p&gt;
&lt;p&gt;25岁，很开心她还陪在我身边。&lt;/p&gt;
&lt;h1&gt;二&lt;/h1&gt;
&lt;p&gt;23年的10月份，我拿着一个月一千的薪水，时不时遭受着导师莫名其妙的人身攻击。因为周遭的氛围太压抑，我总觉得世界的运转规则不应该是如此扭曲，想要出去看看。因此我开启了一段实习，瞒着所有人。&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;strong&gt;Randy的代码之外&lt;/strong&gt;，听&lt;strong&gt;Web Worker&lt;/strong&gt;，偶尔听一听职场相关的内容，比如组织进化论，乱翻书。我开始读Randy的博客，认识了和我一样读研很痛苦的kaiyi，我听到了川哥说：“如果你不开心，千万不要逼自己”。&lt;/p&gt;
&lt;p&gt;因为我一天花在项目上的有效时间可能只有2小时，所以我的导师总觉得我能力太差，他最经常对我说的几句话就是：以我的能力，毕业是个大问题。你的性格有缺陷，可能是你的原生家庭造成的。以后毕业了工作肯定是个废物，什么都干不了。&lt;/p&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;blockquote&gt;
&lt;p&gt;Some birds aren’t meant to be caged. Their feathers are just too bright.&lt;/p&gt;
&lt;p&gt;有些鸟不该关在笼子里，它们的羽毛太丰满。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;25岁，没有人再阻拦你飞向更广阔的天空。&lt;/p&gt;
&lt;h1&gt;三&lt;/h1&gt;
&lt;p&gt;痛苦的硕士并不少见，幸福毕业才是特例。&lt;/p&gt;
&lt;p&gt;读研之前我一腔热血，自命不凡。我总觉得在这里我会开启新世界，我曾经失去的一定会得到。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;硕士三年，我一共做了4次心理咨询。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从这个结果可以看出，这三年我和大多数硕士一样，过得并不开心。&lt;/p&gt;
&lt;p&gt;参加心理咨询没什么不正常的，我反而觉得非常欣慰。我很幸运，我的坏情绪有一个出口，我知道怎么自我调节，从而避免走向更大的深渊。尽管偶尔会被调侃是玻璃人，但我也Fvck off没什么所谓了。每个人对外界的情绪反馈程度都不相同，有的人敏感，有的人钝感。况且如果真的陷入了非常糟糕的坏情绪，不借助外力真的很难脱身。&lt;/p&gt;
&lt;p&gt;但是从另一个角度看我是幸运的。&lt;strong&gt;在试错成本最低的年纪，遇到几个伤害自己的混蛋也没什么不好的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;25岁，我毕业了。很开心，我熬过了这段艰难的时光。生命中可能不会再有这种人出现了，如果还有，我也不再害怕了。&lt;/p&gt;
&lt;h1&gt;四&lt;/h1&gt;
&lt;p&gt;在23年的年底我发了一条朋友圈，庆祝我在给AntV写了20条PR后终于有机会去到杭州和他们约饭。&lt;/p&gt;
&lt;p&gt;但很不幸我因为参加开源贡献被实验室的博士举报，我不知道我做错了什么，但是导师不仅把我痛批了一顿，还打电话给当时AntV的负责人了解情况，这让我非常难堪。&lt;/p&gt;
&lt;p&gt;从那之后我每发一条朋友圈都生怕说错了什么话，做错了什么事，什么言论又给别人留下什么不好的印象。&lt;/p&gt;
&lt;p&gt;我发现真诚的人际关系越来越难得了，在承认这个事实之后我很难过。&lt;/p&gt;
&lt;p&gt;那之后我索性把一些日常的吐槽内容发布到其他社交媒体上，满足一下自己的分享欲。在那里我认识了很多好朋友。&lt;/p&gt;
&lt;p&gt;25岁，“朋友”的界限越来越模糊了。&lt;/p&gt;
&lt;h1&gt;五&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;放弃向他人证明自己，放弃向自己证明自己。专注忘我地去做你应该做的事情，心无旁骛地去解决问题。当你脚踏实地地走自己的路时，那种拼命想要证明什么的冲动就会越来越少。你也会因此变得轻松，自由。&lt;/p&gt;
&lt;p&gt;——查理·芒格&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;25岁，尽管很用力，但你终于不再自命不凡。&lt;/p&gt;
&lt;h1&gt;六&lt;/h1&gt;
&lt;p&gt;让我们来聊点开心的。&lt;/p&gt;
&lt;p&gt;我开始烹饪了，烹饪最有意思的地方是找到自己喜欢吃的食物和调料搭配。比如在尝试的过程中，我发现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;番茄炒蛋的番茄焯水后更嫩，蛋和番茄要分开炒，最后加入Henz的番茄酱，更美味。&lt;/li&gt;
&lt;li&gt;豆角先在油里煸炸后更好吃&lt;/li&gt;
&lt;li&gt;孜然在出锅前5s撒上，搅拌后立马出锅更香&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果不自己做饭不仅对食物的价格没有感知，被动地接受食物也没办法了解自己的喜好。&lt;/p&gt;
&lt;p&gt;25岁我想要尝试做出更多美食。&lt;a href=&quot;https://space.bilibili.com/572164928?spm_id_from=333.337.0.0&quot;&gt;鞑厨高寒&lt;/a&gt;，一位个性鲜明，想要把食客喂饱的厨师曾经说过：&lt;strong&gt;食谱不重要，重要的是了解自己的口味&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;25岁，我开始尝试从口味了解自己，花时间准备一道菜是一件很浪漫的事情。&lt;/p&gt;
&lt;h1&gt;七&lt;/h1&gt;
&lt;p&gt;25岁，我想要创造，渴望美，浪漫，理想主义。&lt;/p&gt;
&lt;p&gt;至于那些曾经嘲笑我，攻击我的人，是你们让我完成了一次又一次的自我摧毁和重建，谢谢你们让我变得更坚强。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但是你们这群杂种，我去你妈的。&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;八&lt;/h1&gt;
&lt;p&gt;谢谢&lt;a href=&quot;https://lutaonan.com/&quot;&gt;Randy&lt;/a&gt;，&lt;a href=&quot;https://skywt.cn/&quot;&gt;SkyWT&lt;/a&gt;，&lt;a href=&quot;https://innei.in&quot;&gt;Innei&lt;/a&gt;，&lt;a href=&quot;https://www.pseudoyu.com/&quot;&gt;Pseudoyu&lt;/a&gt;，读过你们的博客之后，我开始记录，并且爱上这种和自己对话的过程。&lt;/p&gt;
&lt;p&gt;25岁生日快乐，在这个闪耀的年纪，你一定会找到自己想要的。&lt;/p&gt;</content:encoded></item><item><title>2025-重新开始的一切</title><link>https://luckycc.cc/thoughts/16_2025-recap/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/16_2025-recap/</guid><pubDate>Wed, 31 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2025年5月28日，第二天就是我的25岁生日。那天晚上，我在博客上悄悄发了一篇文章，&lt;a href=&quot;https://luckycc.cc/thoughts/15_for25birthday/&quot;&gt;25岁我想说的&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;今年的很多时候，我都会把这篇文章重新翻出来读一读。2025年是我人生中的一个转折点，结束了痛苦的读研生活，不再是一个学生，开始了正式工作，身边的很多事情也都发生了翻天地覆的变化。&lt;/p&gt;
&lt;h1&gt;我毕业了&lt;/h1&gt;
&lt;p&gt;大喜事要写在最开头，我毕业了！这是读研的三年时光中我最期待的时刻，这篇文章我不想要再去一次次地回顾读研时那些不堪的遭遇。我无数次幻想着毕业之后我可以做很多有意思的事情，比如我要置办我的出租屋，养一只属于自己的宠物，我要在自己的房子里做饭，请一大帮朋友来家里玩，到了周末我就找一块草地睡觉，晒太阳，我有能够自由支配的钱做自己想做的事情，买想买的东西。但在毕业的那一刻我才知道，&lt;strong&gt;最重要的是我自由了，我的命运不再攥在别人的手里&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;3月份放完寒假回到学校，那段时间天气很好，我经常去操场晒太阳。我看到好多人铺着野餐垫在操场上打牌，野餐。那段时间我觉得很孤独，想到从23年年底实习到现在，已经一个人单打独斗了好久。&lt;/p&gt;
&lt;p&gt;4月份送完盲审，我和Running开始了为期一个月的&lt;a href=&quot;https://luckycc.cc/thoughts/14_graduation_travel/&quot;&gt;毕业旅行&lt;/a&gt;。我们去了昆明、大理，这些计划了很久的目的地。有一天我们在滇池边上一起散步，那时候的滇池已经没有什么海鸥了。那天下午，她突然收到了盲审没有被抽中的通知，我们一起疯疯癫癫地在滇池边上合影留念。&lt;/p&gt;

&lt;p&gt;毕业旅行的最后一站是三亚，在三亚美高梅酒店的泳池边上，我接到了盲审通过的邮件。一A一B，想到了今年年初的时候我的导师说我毕业是个大问题。&lt;/p&gt;
&lt;p&gt;第二天早上六点，我一个人提着一瓶冰啤酒在海边吹海风，看日出。Running睡到8点，起床跟我一起沿着海岸线走了很远很远。&lt;/p&gt;
&lt;p&gt;紧绷了很久的神经突然放松下来，这是近三年来最轻松的时刻。后来我顶着烈日，在海里玩了一整个上午，因为太匆忙没有好好防晒，整个后背都被晒脱皮了。&lt;/p&gt;

&lt;p&gt;结束毕业旅行后，迎来五一假期。我们一起去参加了好兄弟的婚礼。婚礼的当晚大家喝的东倒西歪，我突然很难过，觉得长大后身边的朋友越来越少。回想起大四最后那段时光，我们一起在南湖边上一起野餐，看日落。毕业那天大家给我送行，告别的时候我们约好以后一定常联系。现如今我们散落在各个城市，只能因为婚礼短暂地聚在一起。5月底，我回学校完成了答辩，跟同门和室友简单告别，连毕业典礼都没参加，就直接去找房子提前开始实习了。&lt;/p&gt;
&lt;p&gt;离开学校那一天，我发了一条朋友圈，上面写道：&lt;strong&gt;有些鸟不该被关在笼子里，他们的羽翼太丰满&lt;/strong&gt;。这句话来源于我最喜欢的电影《肖申克的救赎》。至此，学生生涯就正式结束了，不知道在之后的年终总结里，还会不会时常提及。我有时候还在想会不会三年之后，周围来一个校招生，我也对他说着现在别人对我说的话：刚毕业呀，好年轻啊，要珍惜在学校的时光呀。然后他也向我现在一样对我说：我只想赶紧毕业。&lt;/p&gt;
&lt;h1&gt;工作的变化&lt;/h1&gt;
&lt;p&gt;6月，我正式进入职场，成为一名打工人。正如互联网上铺天盖地的新闻说的那样，AI的井喷式发展改变了一切。&lt;/p&gt;
&lt;p&gt;入职后的第一个需求，我开始承担起前端/后端/数据/产品/设计的工作。大模型时代，一切职能的边界开始模糊。与此同时，工作节奏也变得越来越紧凑。在之前，做完业务需求，会卷框架，卷重构，卷一些优雅的代码设计，归根结底都是在卷一些提效相关的事项。但现在AI来了，提效本身就成了一个伪命题。你为了提升人效做了优雅的代码设计，能比得上AICoding提升的多吗？或者说工程上的提效这个命题，从节省时间，变成了如何让AI更好理解，更容易协作。到了年底，我参与了团队一个的Agent应用项目，开始频繁单休。有些时候能遇到一些很有意思的事情，因为能学到新东西，整个人变得很有热情，状态一下子仿佛回到了去年实习的时候。&lt;/p&gt;
&lt;p&gt;不知道是不是受环境的影响，周围总是有一些“前端已死”的论调在反复出现。如果没有记错的话，这已经是前端死了的第3年。不断涌现的Agent应用，让构建一个产品的门槛越来越低。我时常跟朋友调侃，这种写按钮的工作，我奶奶来都能做了。但回过头来自己又在想，这些工种真的这么容易就能够被取代吗？有一天我在和几个高中同学视频的时候聊到这个话题，我们聊到了过去的三次工业革命。发现都是因为新生产工具的出现，生产关系发生改变。旧需求被生产资料取代，又因为生产资料而产生了新的需求，衍生出新的岗位，岗位的总和不会改变，只是整体做了一次平移。变化真快，去年我还在想要要探索更宽的职业边界，在今年已经成为了不得不做的选择。&lt;strong&gt;前两年我还在抱怨互联网早已没有红利，到了25年我就已经在浪潮之中了&lt;/strong&gt;。&lt;/p&gt;
&lt;h1&gt;两只小猫&lt;/h1&gt;
&lt;p&gt;早在23年我就一直都很想养一只小狗。那时候因为还是学生，没有什么条件，隔三差五我就要跑到狗咖，一待就是一下午，那时候我就知道之后工作我一定会养一只宠物。&lt;/p&gt;
&lt;p&gt;到了后来，因为害怕自己对小狗的陪伴不够多，也有过身边的朋友因为没有精力陪伴把狗送走的经历，所以开始萌生想要养猫的念头。后来又被“大型的宠物情绪更稳定”这种莫名其妙的论调蛊惑，开始想要养大猫。一整个八月，我来来回回辗转了好几家宠物店，加上对象比较怕猫，截止到八月底都一直都没下定要养猫的决心。后来她妥协了，我们一起商量着买了航空箱，猫砂，猫粮，猫玩具…和大多数刚开始养猫的猫家长一样，猫还没到家，已经花了很大一笔钱。后来，经过小红书上的一番检索和攻略，最终决定，上门去看看养户家里一只烟灰色的缅因猫。&lt;/p&gt;
&lt;p&gt;8月31号那天，跟对方约了11点，但因为距离很远，我9点就出发了。那户人家里养了两只缅因，一只黑色的狸花，一只纯白的狮子猫。两只大缅因生了一窝小缅因，卖掉了3只，还剩4只，其中就有那只烟灰色的小缅因，在角落里趴着，刚睡醒。&lt;/p&gt;

&lt;p&gt;经过一番思想斗争，我决定把她接回家，又因为怕她太无聊，又接了一只银灰色的小缅因，是她同窝一起长大的好姐妹。那天她们在一起打闹，互相舔毛。因为不忍心把她们分开，加上工作外出，担心一只猫在家太孤单，所以那天把她们俩一起带回了家。&lt;/p&gt;
&lt;p&gt;回到家后，开始给两只猫起名。稍微黑一点小猫叫 &lt;u&gt;墨可/可可/moki&lt;/u&gt;，因为看到她想到了墨水和巧克力。稍微灰一点的小猫叫 &lt;u&gt;墨点/点点/mody&lt;/u&gt;。因为她的前腿上有黑色的小斑点。&lt;/p&gt;

&lt;p&gt;在中国家庭里，第二个字一般代表辈分。姐妹俩辈分一样，一个大名一个小名一个英文名，非常符合中国家长对孩子的起名方式。&lt;/p&gt;
&lt;p&gt;每天下班回家，两只猫就来门口接我，打游戏的时候在我的怀里打呼噜，周末陪我一起晒太阳。她们长得好快，没过几个月就填满了家里大大小小的盒子。再到了后来，我的相册里也塞满了她们的日常。&lt;/p&gt;
&lt;p&gt;养一只宠物和养一个小孩真是没什么区别。和其他同样养猫的家长有聊不完的话题。你给家里猫吃啥？你平时会带他出门吗？你们多久给他剪一次指甲？就跟养娃的家长聚在一起聊天一样。同时对人的耐心也是一种考验，有时候调皮打翻了水杯，便便沾到毛上，你根本没办法对她们生气，只能默默地把水碗换成更重的石碗，把她屁股上的毛剃掉，避免下次再沾到。&lt;/p&gt;
&lt;h1&gt;新家和朋友&lt;/h1&gt;
&lt;p&gt;在&lt;a href=&quot;https://luckycc.cc/thoughts/w1_the_new_journey/&quot;&gt;踏上新的旅程&lt;/a&gt;这篇文章中我谈到过，居住体验大于一切，因为日常生活体验会直接影响工作体验和表现，工作也会反向影响日常生活体验。在租房上一些舍弃一些成本去换取便捷性和舒适度是一笔非常值得的买卖，所以在找房子这件事情上，我们花了很多时间。&lt;/p&gt;
&lt;p&gt;最后，我在离公司3公里的地方，租了一个90平的三室一厅，价格3700。房子里有一间主卧，一间客房，还有一间活动室。虽然是租的，但是我还是花了很多时间和精力布置着我的第一个房子。在刚租到的房子的那半个月，我还没有上班，每天都在测量房子的尺寸，规划房子的不同区域应该怎么布置。活动室我布置成了一个书房，提供回家后处理一些紧急工作/早上浏览一些资讯和文章/周末打打游戏的空间和条件。我还配了一台电脑，因为不想花时间折腾，所以直接买了一台整机。&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;h1&gt;康复和饮食上的改变&lt;/h1&gt;
&lt;p&gt;去年11月，我完成了半月板重建手术，&lt;a href=&quot;https://luckycc.cc/thoughts/10_meniscus_surgery/&quot;&gt;半月板手术记录&lt;/a&gt;。手术后我在日程下加了一条“宇宙重建计划”，希望在不远后的将来，我能够健康地站在球场上。&lt;/p&gt;
&lt;p&gt;在1月份脱拐之后，我第一时间找到了曾经给我做过康复的康复师，开始了漫长的康复。康复的过程最折磨的不是炎症带来的疼痛，而是精神上的痛苦。一些伤前能够轻松完成的动作，由于疼痛和活动度受限，没有办法再完成。到了康复后期，我开始恢复球类运动，这种感受更强烈。每次打完一场球，我都会陷入“现在怎么变成这样了”的焦虑和恐惧中。&lt;/p&gt;
&lt;p&gt;这种心态一直持续到了9月，我太迫切想要回到之前的竞技水平，9月我开始了高强度的力量训练。从那之后我的运动量越堆越多，有时候中午打完两小时球，晚上下班了还要去健身房做力量，每周五都要去球馆投进一百个三分。为此我还制定了一份冬训计划，想着坚持练完，春天肯定会不一样。但我还是高估了自己的身体，半月板的强度由于手术降低了很多。11月，随着运动强度的增大，膝盖总是酸胀。我又去医院拍了个核磁，结果不是很理想，医生告诉我我的膝盖不太适合再剧烈运动了，建议我减重。到了12月，我又重新开始了漫长的康复历程。&lt;/p&gt;
&lt;p&gt;当时受伤的时候，我的体重是89kg，目标设置在83kg，当前85kg。减重其实不难，难的是维持和控制。维持体重，需要从作息，饮食，运动习惯上做出改变和牺牲。减重的原理老生常谈，制造热量缺口，让热量入不敷出。但是严格计算热量的摄入与支出不是件容易的事情，只能根据晚上的饥饿感来判断。在年末我还实践了一套适合自己的饮食方案，包括把高热量的早餐替换为坚果，减少果糖摄入，以及每个工作日的晚上都选择轻食。这里可以展开谈谈吃轻食，除了会掉称之外，工作日晚上吃轻食还有两个意外的好处。&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;h1&gt;今年读过的书&lt;/h1&gt;
&lt;p&gt;Randy在他的博客里面写：“如果一段时间我读了很多书，说明我这段时间过得不太好”，我觉得我差不多也是这样。今年开始阅读的书大概10本左右，读完了4-5本。读书的时间是去年的一半，除了有点懒了之外，还有一个原因可能是毕业后，我过得很幸福。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;我觉得&lt;strong&gt;人的品味会成为这个时代的一条护城河&lt;/strong&gt;，所以和以往不同的是，今年我开始功利性地读一些“设计”和“产品”相关的书。因为很喜欢交互设计，在3月份的时候，我甚至想要开始学figma。&lt;/p&gt;
&lt;p&gt;今年最推荐的书是5月份读完的《献给阿尔吉侬的花束》，讲了一个低智商的人类智商不断升高的过程，生活中发生的变化。书中有一句话印象很深刻，“如果没有人性情感的调和，智慧与教育根本毫无价值”。&lt;/p&gt;
&lt;p&gt;给我感触最深的一本书是《研磨记》，讲的是一位斯坦福博士的读博经历。感触很深的原因是，他在读博过程中遇到的很多困难，和作出的很多选择都让我想起了我的硕士生涯。读完后我立马分享给了我一个正在读博的好兄弟，希望他26年能顺利毕业。&lt;/p&gt;
&lt;p&gt;3月，我读完了高中听文化有限播客时被安利的一本书《平面国》，当时只是觉得书中描述的平面国世界蛮有意思的，直到数年后再次在微信读书中邂逅这本书，决定开始阅读，读完的心情就像当年我听到那期播客时一样，不得不佩服作者的脑洞。4月，我去做了一次心理咨询。那时候我刚读完《被讨厌的勇气》，去和心理咨询师聊了聊阿德勒心理学。书中有一个观点我很赞同，就是我们要跳脱出”赏罚体系“，避免让一切行为的动机都变成希望得到奖赏和避免得到惩罚。&lt;/p&gt;
&lt;p&gt;年初我浅浅翻阅了《冲突的真相》，那时候我因为压力太大差点吵架分手，所以开始阅读一些关于亲密关系的书籍。年中，因为觉得自己设计的页面缺乏美，我用NotebookLM开始学习《Refactoring UI》。因为开始进入职场，我开始读《纳瓦尔宝典》、《消失的多巴胺》。《为什么精英都是时间控》是我最近在读的一本书，书中强调时间不在于管理而在于利用。读了这本书之后，我开始更关注自己日常的注意力，可惜喊了大半年的早起学习，最后也没能成功几天。&lt;/p&gt;
&lt;h1&gt;我的AI工具使用&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;3-6月，因为要写毕业论文，订阅ChatGPT Pro。20 USD/mon&lt;/li&gt;
&lt;li&gt;5月，用GPT做了一次心理咨询，&lt;a href=&quot;https://luckycc.cc/thoughts/13_letter_forme/&quot;&gt;记一次心理疗愈&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;7月，因为开始工作，订阅了Cursor，20 USD/mon&lt;/li&gt;
&lt;li&gt;11月，Cursor被封，公司内场使用Trae&lt;/li&gt;
&lt;li&gt;12月，订阅Claude，20 USD/mon&lt;/li&gt;
&lt;li&gt;12月，上了Soon的Gemini家庭车，100USD/year&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;年底，我给ChatGPT和Claude喂了一段Prompt&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我每年元旦都会给下一年的自己写一封邮件。 在这封信里，我会100%对自己诚实，记录这一年里真实的收获、成长、成就、判断、思考、困局、犹豫和未解决的问题。同时，我也会认真思考下一年，在人生各个重要的重要维度上，我真正想要的是什么，以及我可能需要面对的选择与代价。 我也希望你能以一个长期旁观者、冷静、非常毒鸡汤的视角。不要鼓励、不回避尖锐问题的，能够真正提醒我一些我容易忽略、回避或自我合理化的地方。希望你是客观描述和评价的。帮我回忆尽可能更多的事实和细节。&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;ul&gt;
&lt;li&gt;对应我在年中问过他一些MacOS开发相关的事情，年末问过他们一些Agent设计，Rxjs设计问题，但没拿出一个产品&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;篮球教会了我自律、对抗。但它也给了我一个错觉——只要足够努力，就能掌控一切。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;对应我在年末跟GPT聊天，跟他说再次受伤让我觉得一切努力都白费了，我很难过。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;我害怕的不是失败,是”全力以赴后依然平庸”。我在用”追求完美”来回避”被检验”。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;不知道对应啥，但是应该对应的是我总是跟他聊一些技术，但是都没实现。但是我觉得他说的有点道理&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2025精选时刻&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;3月参加D2大会，主题是热AI，所有的大会都在聊AI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正式入职第一天，在书房拍了张照片做纪念，那时候我还第一次尝试了蓝色的挂耳染。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;家里最大的一次party，一共10人，就是这次派对开始接触到匿名真心话这个游戏&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;10月去参加了卡卡的葬礼，卡卡是朋友家养的一只40斤重的狸花猫，他因为一场意外去了喵星，祝他下个喵生一切都好，&lt;a href=&quot;https://hong97.ltd/blog/markdowns/gei-wo-de-miao-ka-1759326238195?key=gei-wo-de-miao-ka-1759326238195&quot;&gt;他的主人写给他的信&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;年底用Nano Banana生成的一张照片，是我抱着可可和点点，她们真可爱&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h1&gt;没有主题的废话&lt;/h1&gt;
&lt;p&gt;去年我用n8n做了一个信息流的workflow，但是后面因为telegram的账号被封，也不了了之。有一天加班到11点下班回家，发现博客突然打不开了，排查了半天发现是代理污染了DNS，折腾到凌晨2点才睡着。今年的开源贡献是0，不再像23年那样热衷于打榜PR数和刷满一页绿的贡献量。但还是偶尔刷一刷github，看看有什么好玩的东西。&lt;/p&gt;
&lt;p&gt;在痛苦的时候，时间是客观的。24年我觉得时间过得太慢，在日程表上写满了密密麻麻的规划，数着时间过日子。幸福的时候，时间是主观的。今年的最后一个周末我坐在咖啡馆闭关写作这篇文章，感慨时间过得真的太快，回顾起来今年的很多时候，都觉得每一天都很比去年更有掌控感。但是可惜工作之后生活平平淡淡，也很难再留下什么深刻的记忆点。&lt;/p&gt;
&lt;p&gt;感谢阅读，以上就是今年重新开始的一切。祝福各位2026年一切顺利，26年希望自己在这个浮躁的时代不要放弃对真善美的追求，&lt;strong&gt;不过什么事情都跨完年再说吧&lt;/strong&gt;！&lt;/p&gt;</content:encoded></item><item><title>写在26岁生日</title><link>https://luckycc.cc/thoughts/17_fo26birthday/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/17_fo26birthday/</guid><pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;现在是2026年5月29日晚上11点，刚刚和朋友们一起吃烧烤，回到家洗完澡。&lt;/p&gt;
&lt;p&gt;今天是我的26岁生日。&lt;/p&gt;
&lt;p&gt;昨晚创建了这个文档，但是因为工作太疲劳，刚写下两行就已经力竭了。读了读25岁生日自己写的博客，本来以为26岁不会有文字记录，在无人在意的一天过一个平平淡淡的生日就好，但是想想还是有些不甘心。决定还是写下一些文字，记录一下新的一岁。&lt;/p&gt;
&lt;h1&gt;一&lt;/h1&gt;
&lt;p&gt;在今年的2月，我结束了一段长达6.5年的恋爱。在工作同居之后我们的矛盾几乎变得不可调和，决定不再继续。&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;那段时间我的状态非常糟糕，工作压力很大，持续单休。每天我几乎疯一样地跟AI对话，得到的还是那几句公式化的回答。后来，我主动地逼自己做一些断舍离，趁着情绪崩溃的时刻逐渐清理一些旧的物品，一些照片。到了5月，我买了车，租了新房子，搬离了我们一起住过的地方。&lt;/p&gt;
&lt;p&gt;我翻看了23年，24年，25年的年终总结，还有25年生日我写的博客，每年都会提到她。在刚刚分开的那段时间里，我几乎不敢浏览那些文字，想要删掉这些内容，或是在这些内容旁边补充一些说明，但总觉得这样太刻意，读起来不舒服。这件事情就这样拖到了今天，我已经不想去修改这些文字了。那些一起经历过的美好就这样用文字记录下来，之后读起来也会觉得当时很开心，幸福，这就足够了。&lt;/p&gt;
&lt;p&gt;下面这些文字很矫情但是还是想写一下。&lt;/p&gt;
&lt;p&gt;相互陪伴了这么多年，我们一起经历了很多过程，我真心感谢她。我觉得一段好的关系就是能够更加看清自己，了解自己更渴望和会付出哪种爱的语言。这段感情让我本就不乐观的婚姻观又悲观了很多，什么样的关系才是我想要的我不想再思考。后来我看到李诞说的一句话我特别认同：你这辈子可能再也遇不到这么合适的人了，但是又有什么关系？&lt;strong&gt;只要关注自己有没有一天比一天变得更好就行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;26岁希望自己可以在情绪上更独立一些，更关注自己的主体性，一个人也要过的丰盈，充实。&lt;/p&gt;
&lt;h1&gt;二&lt;/h1&gt;
&lt;p&gt;26岁我成功减重12斤，从三月的88.6kg减到了现在的81.4kg，距离目标体重80kg还有3斤。&lt;/p&gt;

&lt;p&gt;虽然说失恋的痛苦加速了这个进程，但是我还是付出了很多努力。我一直坚持着16+8轻断食，每顿在公司食堂只选择水煮菜+牛肉的套餐，坚持了3个月，效果显著。因为轻断食的习惯，我对高糖和油腻的食品也完全失去了兴趣，饮食习惯调教得非常健康。&lt;/p&gt;
&lt;p&gt;起初开始16+8的时候，身体非常不适应，每天晚上饿得根本睡不着觉。那段时间恰逢失恋，持续加班，许多事情夹杂在一起，压力暴增。这种时候人的身体会对碳水产生非常强烈的渴望，很多时候加班回到家很想急头白脸吃一顿宵夜犒劳一下自己，但是都忍住了。&lt;/p&gt;
&lt;p&gt;我觉得自己还挺有毅力的，看着自己一点点掉秤，肌肉的刻度逐渐显示，真的是一件非常有成就感的事情。&lt;/p&gt;
&lt;p&gt;26岁希望自己能够继续保持健康的生活方式，像对待跑车一样对待自己的身体，加最好的机油，做最好的保养。&lt;/p&gt;
&lt;h1&gt;三&lt;/h1&gt;
&lt;p&gt;26岁完成了自己的一个心愿：拥有一辆车，是一辆黑色的ModelY，在APP里我给他起的名字叫马车，牛马开马车。&lt;/p&gt;
&lt;p&gt;买车前我做了非常久的心理斗争，我一直在算一笔账，有没有必要买，买了之后我能不能负担，我的生活质量会不会变高。最后下定决心是四月底，我发现再不买车就没法搬家了，就去找销售下订了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;有了车之后的生活发生了翻天覆地的变化，车扩大了我的可活动半径，给我带来了物超所值的体验。之前5公里之外的地方我根本不想打车前往，但是现在就是一脚电门的事情；周末加班的话早上可以开车去球馆练个球，获得半个周末的体验；去宜家购置喜欢的家居；不再风吹日晒雨淋，获得地库到地库的通勤体验。&lt;/p&gt;
&lt;p&gt;有时候下班太累了不想上楼就在车里听听歌，打打电话，车成了我的第二个家。&lt;/p&gt;
&lt;p&gt;我还记得一天工作日的早上，再去公司的路途中我一边听着蛋堡的《I Want You》，一边喝着从家里带的柠檬水，一路上阳光很好，&lt;strong&gt;我觉得赚钱的意义不过于此了&lt;/strong&gt;。&lt;/p&gt;
&lt;h1&gt;四&lt;/h1&gt;
&lt;p&gt;独居之后我搬到了一个脱离了房补范围，面积更大的房子。我深刻体会过居住体验对一切的影响，因此不愿意在房租上缩减任何成本。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;我开始更关注把钱花在情绪上。&lt;/p&gt;
&lt;p&gt;最近买了一台扫地机器人，只是为了能让我每天回到家能够看到反光木地板。买了一个客厅的落地灯为了营造一些暗光微醺Vibe。买了一个二手的SteamDeck Oled 1TB只是为了让自己过年回家不再无聊到跟乱八七糟的亲戚扯淡。很多物品在购买的时候我都不再考虑他们的实用性，而优先考虑的是我拥有他们会不会变得开心。&lt;/p&gt;
&lt;p&gt;可能是工作后有了一些储蓄的原因，我觉察到了26岁，在消费观上一点不一样的改变。&lt;/p&gt;
&lt;h1&gt;五&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;最近一两周的状态很好，每天都有7.5小时左右90分以上的睡眠。因为睡眠质量一直不是特别好，因此近几个月我有在强迫自己把睡眠当成一项训练，尝试过一些补剂，有一些效果。&lt;/li&gt;
&lt;li&gt;和朋友一起去玩了攀岩，觉得很有意思，有一种解题的快感，上了一节体验课后立马下单了一张月卡。&lt;/li&gt;
&lt;li&gt;最近工作挺忙的，压力也很大，五月份连续单休。后面工作可能会越来越忙，但是不要担心，就像24年年终博客里说的那样，给自己的生活找到更多的“支点”就好。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;可能和更新的时间点有关，每当我写博客的时候都总是感叹一下时间过的好快。&lt;/p&gt;
&lt;p&gt;26岁博客没有25岁那样情感那么强烈了，可能是阅读量跟不上了，也可能是本身这就是普通的一天，一次平淡的生日。珍惜跟自己对话，总结的机会。&lt;/p&gt;
&lt;p&gt;与25岁不同，26岁我不再只是期待“熬到某一天”。&lt;/p&gt;
&lt;p&gt;从今天开始，我要更频繁地感受到，&lt;strong&gt;我活在我亲手选择的生活里&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;26岁生日快乐！希望在26岁，甚至更久，都能永远保持创造的热情，对美的渴望。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;想要就去争取，无论会不会得到。&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title>撕开我的伤口看看</title><link>https://luckycc.cc/thoughts/18_srbirthday/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/18_srbirthday/</guid><pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今天是你的生日。
刚刚结束完上海的差旅，拖着行李回到家中。瘫坐在沙发上，不停地刷新着你的iMessage，发现我在凌晨十二点准时给你发出的生日祝福，仍然已读未回。
在5月28号那天，你说你买了3个蛋糕，想给我过个生日，我拒绝了。理由是这个26岁生日我想自己度过。7月27日，我提前两周问你生日那天要不要一起吃饭，你问我为啥，我不知道怎么回答你。是啊为啥呢？我为啥要约你吃饭。过了一周我告诉你，我说想和你过个生日，你说不要了，见面又是反复内耗和拉扯。今天是8月7号，轮到我来体会你当时的感受。&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;某个周六的早晨你在厨房做早饭，我们一起拿到阳台边晒太阳边品尝，两只猫一只睡在你的怀里，一只睡在我的脚边。毕业旅行我们一起玩了好几个城市，在滇池边上散步，收到你盲审通过的消息。在洱海边上，我说好想时间永远停在这里。我们在迪士尼之旅后说了分手，但是半月板手术你还是从上海坐一晚上的硬座过来陪我，你说如果你不来你会遗憾，你担心我。你记得你连吃了5顿我做的洋葱青椒牛肉后哭了吗，你哭着说为什么我只会做这个。后来我做的仍然是一些其他的家常菜，但朋友来家里，你还是很开心地和他们介绍桌子上的这些这些那些是我会做的拿手菜。&lt;/p&gt;
&lt;p&gt;我忘不了这些，忘不了第一次带你回家，我给你录下局促的神情。保研的时候你说你想出国，我说如果和你异国我会很难过，你写了一封信告诉我你这么做都是为了我们以后的生活。你记得你收到同济的录取通知书的时候，我们一起视频开箱吗。后来我们异地了，分别的第一周，我说我好怀念之前能骑电动车载你在校园里漫游的夜晚，我好想你。你说你也是，然后我们一起安排见面的时间。秋招的时候我拿了数不清的offer，我说我们会在杭州有一个幸福的小家，我们会养两只猫，然后在26年的那个元旦，我们会一起抱着她们拍一张全家福。
再之前，你还记得吗？大二的时候你学完拳击课我们在五食堂门口你兴奋地告诉我你这节课学了什么。你在考前做题错得太多会生气，你当时说和我恋爱耽误你六级考试，你需要一个能督促你进步的人而不是带着他前进的人。你记得我和你表白的那天吗？电影播完了我都没有勇气告诉你我喜欢你，然后我带你偷偷跑进另外一个影厅，那个影厅播放着五月天的纪录片，电影结束后，我拉着你的手问你愿不愿意做我女朋友，你说你可能不太会经营感情但是可以试试。这些曾经的回忆，想到都觉得好幸福。但是他们现在变成了身上的锁链，让我没法呼吸。&lt;/p&gt;
&lt;p&gt;但是我们都知道的，回忆只是带有滤镜的记忆对吗？我们还有很多不可调和的矛盾对吗？我们都已经把对方伤害得没有办法再原谅对方了对吗？那种低能量的生活是我们都不愿意再回到的对吗？可是为什么，为什么这些回忆还是会在这些孤独的夜晚，这些我渴望拥抱的夜晚，在我尝试接触新人之后，一次又一次，一次又一次地浮现在我的脑海里。&lt;/p&gt;
&lt;p&gt;在很早之前，我一直安慰自己。会好的会好的，他们都会过去的，让一切流动让一切发生，相信时间的力量，但我觉得这些都他妈是狗屁。
好在哪？我能好到哪去？
你知道吗，很多时候，我都在期待哪天我从朋友的口中得知，你开启了一段新的恋情，再经历一次暴雨我就能彻底放下对这段关系了。有时候朋友劝我和好吧，我说我不要再回到那种状态了。但有时候我又想和你再见一面，一起吃饭聊聊工作，问你最近过的好吗。说真的，我看不起自己这个样子。就好像那些被背叛，家暴伤害过的人，还在央求对方你不要这样了，你好好再爱我一次吧一样。我不愿意看到自己这幅模样，但是他妈的我是被下咒了吗？&lt;/p&gt;
&lt;p&gt;这些矫揉造作的文字，再一次地，伴随着眼泪呕出了我的身体。用这种方式反刍一段感情真的能让自己变好吗？写下来，说出来，我就能够想明白吗？但是他妈的，我还有什么地方想不明白？这些说破了的大道理，你去问问哪个人想不明白。&lt;/p&gt;
&lt;p&gt;2026.08.07&lt;/p&gt;
&lt;p&gt;Cee.&lt;/p&gt;</content:encoded></item><item><title>听谷歌设计师Riceman访谈有感</title><link>https://luckycc.cc/thoughts/2_RicemanThought/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/2_RicemanThought/</guid><pubDate>Sat, 29 Apr 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今早听到一篇博客：&lt;a href=&quot;https://podcasts.apple.com/cn/podcast/%E4%BB%8E%E9%9B%B6%E9%81%93%E4%B8%80/id1241589761&quot;&gt;S2E12 | 谷歌设计师和他的苹果最佳播客 | 黄季业 Riceman从零道一&lt;/a&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;li&gt;终身仁慈独裁者（BDFL）的管理模式&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;设计是一个多方博弈的过程&lt;/h1&gt;
&lt;p&gt;Riceman在博客中提到，设计是一个多方博弈的过程。怎么理解这句话 ？把自己代入一个TikTok的设计师/产品经理。&lt;/p&gt;
&lt;p&gt;为了给用户带来绝佳的短视频浏览体验，那么必然需要削减广告投放的数量与时长。但是这么干老板肯定不愿意了，老板一看，广告投少了谁给你发工资啊？出于资本家的角度，并不care你使用什么方式留存用户，反正就是要创造最大利润，最大利益。对于设计师自己，做了这么多的产品，在TikTok中肯定想要融入自己的一些设计理念。比如30秒的广告尽可能少投放，5秒的广告适度投放。或者比如Youtube，可以允许用户在5秒后自由选择关闭或继续观看广告。&lt;/p&gt;
&lt;p&gt;所以，设计一个产品其实需要站在三个维度思考问题。对用户：这个产品怎么才能吸引我；对老板：这个产品怎么才能给我赚到更多的钱；对设计师自己：这个产品如何才能体现出我的设计理念。这么看，产品经理和设计师似乎比研发同学压力要大很多。&lt;/p&gt;
&lt;p&gt;Riceman的这个观点挺有意思的，在听到这里的时候我很迅速地就想起来了我前阵子刚做完的一个华为云的项目：&lt;a href=&quot;https://github.com/zqqcee/dataviz&quot;&gt;Multi-View Visualization Method for Cloud network&lt;/a&gt;。翻译过来是：云网络场景下的多视图协同可视化方法。很酷的名字，感兴趣的朋友可以到我的仓库中看看。个人觉得是我一个比较拿得出手的项目，集成了数据分析、链路探索与多视图协同多个板块，也算是我做的第一个图析平台了。但是，既然我能po到github仓库上，说明我的这种方案肯定最终没有被落地到华为云的网络大脑的系统中上线使用。还挺难过的，因为我觉得各方面都蛮好。其实我们设计了两套方案，出于一些业务的考虑，客户选择了另外一套。&lt;/p&gt;
&lt;p&gt;为什么我想起了这个项目？很简单，当时我其实也在做博弈。对于华为云的这个项目，我算是个小leader吧，和客户沟通，把控进度，和客户做汇报都是我来负责。那么自然而然地，我承担起了leader、项目经理、研发多重任务。因为网络大脑这个项目比较青涩，公司那边的需求比较明确，就是想要一种直观地可视化方式，布局简洁美观，故障设备的分布一目了然。但其实这需求又不是那么明确，如何展示？如何定义直观？如何布局？都是不知道的。准确点说，公司是给了我们一个想要达成的目标，并且为这个目标的实现过程添加了一些约束（业务知识，比如设备的层级关系，分Region、AZ、POD展示等），其余我们自由发挥。&lt;/p&gt;
&lt;p&gt;一听到自由发挥那我高兴了啊，我不就是这个设计师吗？公司就是客户，我的老板就是我的导师。客户想要简洁高效，我的导师想要客户满意（因为合同已经签了，金额已经确定了，客户满意了才能收到钱），那我呢？因为是第一次做大项目，还蛮激动的，我既想要客户能够满意，我的导师能够满意，还想要这个交互系统技术上不要搞得那么难，毕竟我不想压力那么大。&lt;/p&gt;
&lt;p&gt;于是，经过了大约十余次次的沟通，我逐步引导客户往多视图协同上走，一切进展地非常顺利。但是…&lt;/p&gt;
&lt;p&gt;好巧不巧，网络大脑部门负责人换了，原来的负责人描述不清楚他们的需求，所以我们提什么方案他都答应。换了个新的负责人，是从研发线晋升上来的，比较熟悉业务，给我们推翻了先前的方案，说是：“这种几何割裂的层级分割方式虽然在几何上很美，但是却不是我们运维人员想要的结构”。后来经过一番了解才发现，他们居然想要的是：树形结构～！&lt;/p&gt;
&lt;p&gt;对我们来说，这简直是灾难，我们努力了半年的结果居然不是他们想要的。但是这个问题后续还是得到了妥善的解决，自然而然地，我们提出的早先方案就没有被接受。我觉得丢弃了有些太可惜了，就好好写了个README传上了我的仓库。在这场博弈中，我可能没有完全站在用户的角度考虑问题，或许是我不该引导他们接受我们设计的方案，而应该引导他们说出他们心中最理想的方案。这是我做的不好的地方，但是给用户种草这个思路也是一种方式，我觉得这点我做的还是OK的。&lt;/p&gt;
&lt;h1&gt;产品的生命 &amp;amp; 终身仁慈独裁者（BDFL）的管理模式&lt;/h1&gt;
&lt;p&gt;这两点其实是最近在给G6提PR的时候，扫了一眼G6的issue list。发现有一些用再提增加xxxxx功能的请求，联想到的一个话题。博客中有提到：如果做产品的时候一直只关注提出负面反馈的用户，那么产品最终也会走向失败。这其中其实存在一定的幸存者偏差，假设有100个人使用了产品，有10个人不满意，有5个人反馈了为什么不满意。那么如果一直照着用户的反馈意见修改产品，反而可能会让产品越来越糟糕。在先前听 &lt;a href=&quot;https://antfu.me/&quot;&gt;antfu&lt;/a&gt;讲开源手册的时候就了解到，并不是每一个给社区提的PR都会被接收的，这种时候就需要尊重并且理解社区的决定，有时候并不是这个想法不好，可能只是不满足社区发展的方向。&lt;/p&gt;
&lt;p&gt;终身仁慈独裁者（BDFL）是我前两天在一个访谈尤雨溪的节目中了解到的一个词，通常是指社区中对于一个项目拥有最高决策权的人，负责拍板社区的重大或有争议的决策。&lt;/p&gt;
&lt;h1&gt;国内开源模式&lt;/h1&gt;
&lt;p&gt;感觉国内的开源产品很容易走向自身业务主导的道路，做出来的产品很可能只match自己公司的业务，而并不能做成一个面向社区的开源产品。不过Umi.js似乎是一个成功的典例，Umi是&lt;a href=&quot;https://github.com/sorrycc&quot;&gt;云谦 sorrycc&lt;/a&gt;老师的作品，一个React极速开发框架。对于内部的业务，基于Umi封装了Bigfish，专门在业务层做输出。前阵子刷到偏右老师的知乎&lt;a href=&quot;https://zhuanlan.zhihu.com/p/64214581&quot;&gt;那些年的体验科技部&lt;/a&gt;，了解到Umi和Bigfish之前居然是竞争关系，你死我活，后来联手做了这么一种开源模式，一个主外一个主内，感觉还是挺有意思的。&lt;/p&gt;</content:encoded></item><item><title>How to be a good Project Manager?</title><link>https://luckycc.cc/thoughts/3_goodpm/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/3_goodpm/</guid><pubDate>Sun, 28 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;昨天X上看了&lt;a href=&quot;https://twitter.com/dotey&quot;&gt;宝玉哥(@dotey)&lt;/a&gt;的一则post，大概讲的是他一起合作了一段时间的一位工作能力超棒，有口皆碑的PM离职了。全篇整理了怎么从优秀的PM身上找出他们的闪光点，还有如何向他们学习。&lt;/p&gt;
&lt;p&gt;文章我读了两遍，第一遍读的时候我想的是现在正在合作的校企项目中企业方的项目经理，坦白来说这个项目双方都不是很愉快，或者说表面上挺愉快的，暗地里波涛汹涌。第二遍读是今天，今天stacey愁眉苦脸的，和我说她正在负责的比赛，团队里的两个小伙伴在疯狂开摆，感觉和她们一点关系都没有似的。我一边安慰她一边在思考到底怎么样才算一个优秀的管理者。回家以后立马找了宝玉哥的推文，重读一遍，收获很大，刚好博客的talks栏目好久没更新了，囤了一大堆主题，所以趁着这个话题我也蛮感兴趣的，做一个整理。&lt;/p&gt;
&lt;h3&gt;Intro&lt;/h3&gt;
&lt;p&gt;遇到过两个不熟悉产品的PM，在同一个项目中。这个项目是一个产学研项目，涉及到三方协作：校方、公司A部门，公司B部门。合作逻辑是这样的：公司A部门和B部门的共同leader拉通了A部门和B部门的合作。大致内容是A部门想让B部门在他们的云服务的运维系统中添加一些可视化能力。B部门找到学校（也就是我们），合作。&lt;/p&gt;
&lt;p&gt;本来写了一堆Intro，但是看了看发现都是吐槽B部门的PM，想想算了。对于一切事物&lt;strong&gt;择优录取就好&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;Concept&lt;/h3&gt;
&lt;p&gt;提炼几个Key Points和我的切身感受吧&lt;/p&gt;
&lt;h5&gt;熟悉产品&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;熟悉产品是一个PM最基本的技能之一了吧，遇到过一个完全不了解产品的leader（说实话我也很好奇他到底是怎么当上这个leader的）。熟悉产品这是对一个PM最最基本的要求了，不然需求过来了都不知道怎么排期，也没法对产品后续的发展作出一个好的规划。我甚至觉得好的PM要具备一些提前预知产品走向和决策的能力，就像科研人需要拥有科研嗅觉一样，我也在很努力地学习这一点。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;处理需求优先级&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;在产品的发展过程中，会有很多突然“加塞”的需求，如何**合理无痛**地处理这些需求的优先级则是彰显一个PM能力的时刻了。如何让一个团队拥有很好地所谓“抗加塞”能力，工作有条不紊地进行是非常非常难的。因为有时候需要拒绝，有时候需要和需求方battle，甚至面对一些非常紧急的需求，如何传达给员工，如何合理地排期，如何**无痛加塞**都是管理的艺术。&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;加塞的需求可以分个类，&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;紧急：尽量第一时间处理，没什么好说的&lt;/p&gt;
&lt;p&gt;功能性需求：&lt;/p&gt;
&lt;p&gt;默认放在下一阶段（原文为Sprint），需要和提需求的人做好沟通很重要，要表达清楚发布流程，以及可能对当前工作flow造成的影响。（PS：沟通真的挺重要的）&lt;/p&gt;
&lt;p&gt;技术债务与性能优化&lt;/p&gt;
&lt;p&gt;优先级较低，在阶段性排期时，&lt;strong&gt;需要安排20%-40%的时间来处理技术债务以及优化工作&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这点是非常值得学习的，对我来说就是定期找人CR，或者组员间相互CR。定期汇报与评估等等&lt;/p&gt;
&lt;p&gt;大领导空降加塞&lt;/p&gt;
&lt;p&gt;这种情况是最难handle的，事实上我遇到的最多也是这种情况，基本上公司和老师打个招呼，说需要加某某功能。校方说得好听点是合作方，说得难听点就是个臭干活的（hh:P）。所以在这种时候就会出现很多空降加塞，而且更恐怖的是一些不太懂技术的加一些看起来很简单但是由于历史架构等原因根本完不成的需求。&lt;/p&gt;
&lt;p&gt;做法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;要先搞清楚他们紧急加需求的原因是什么？ 然后要让领导们清楚这样临时加塞所造成的影响是什么，比如会让系统不稳定，会影响其他正在进行中的计划，或者其他影响。&lt;/li&gt;
&lt;li&gt;最后如果领导坚持要加塞，&lt;strong&gt;没必要正面对抗&lt;/strong&gt;，可以找PM或者其他领导一起协商，实在不行就给加塞上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;没必要正面对抗！确实想到之前我没有办法处理这种情况的时候都会产生非常强烈的抵触情绪，然后就会演变成言语冲突，正面对抗。现在想想还蛮小丑的，下次需要想想办法沟通一下了。&lt;/p&gt;
&lt;p&gt;但是我不同意实在不行就加塞上这个说法，因为加塞上意味着责任转移了，如果“实在不行”，我认为需要表达为什么不行，一直掰扯到这件事情能够找到一个合理的解法后，再加塞。&lt;/p&gt;
&lt;h5&gt;提前计划&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;再有就是这个PM对于新产品功能，有很好的计划性，能提前协调好资源，在将需求交给开发时，UI/UX设计、后端API这些都已经准备好了，各种细节也通过文档和多次会议反复确认完成，开发人员只要按照产品设计文档、UI设计文档、API文档去开发就可以，不需要开发自己去反复确认。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;一个项目的排期是必须的，也是极其重要的。但是”在将需求交给开发时，xxxx全部准备好“这个是非常重要的。事实上这点我早在大三参加服务外包创新创业大赛的时候就有体会到，前期准备是非常重要的。一个还没完全理清楚思路，没有充分准备就开始的产品，干得再多也只是给团队徒增债务罢了。所有的债务后面都需要一项项偿还（血的教训）。&lt;/p&gt;
&lt;p&gt;制定计划的时候不仅要考虑到项目的总体交付目标，还需要考虑到每一位项目组成员的情况。过紧的计划会导致产品发布后期团队无力，疲惫堆积严重，而过松的计划也会导致这点。这就需要PM有一个合理排期的能力，这是一项不可或缺的职业素养。&lt;/p&gt;
&lt;h3&gt;Addition&lt;/h3&gt;
&lt;p&gt;下面是原推中没有出现的内容，但是我认为一个PM必须要具备的内容。不是说原文落了这些points，因为可能以下这些品质是最最基础的品质。只是我想记录在这里&lt;/p&gt;
&lt;h5&gt;找到团队的意义与个人的意义&lt;/h5&gt;
&lt;p&gt;最最重要的一点，尤其是在没有步入社会，还是在学校的朋友们在带领团队做比赛/做项目/写论文的时候要时刻思考的一点。&lt;/p&gt;
&lt;p&gt;就是：&lt;strong&gt;这个项目能为 他/她/你/我 带来什么？这个项目对他们的意义是什么？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是一个很重要的点，和社会上工作不一样，工作了有钱拿，不工作就会失业。再怎么没有意义，钱也是意义。但是在学校不一样，往往大家在学校参加项目的目的都是能够增长自己的见识。那么试问如果让一个成员每天对着电脑copy&amp;amp;paste，谁愿意接这个活？&lt;/p&gt;
&lt;p&gt;就我而言，在我给组员布置任务的时，我都在想以下几个问题：&lt;/p&gt;
&lt;p&gt;(1) 会不会太难了？｜ 如果一项工作难度太大，会打击自信心，也会让接下来的一切工作都失去活力&lt;/p&gt;
&lt;p&gt;(2) 做不完我能不能兜底？｜ 这个问题影响着我会不会挨老板批，所以我一般会要求定期做个汇报看看进度，如果走上正轨了就会减少汇报次数&lt;/p&gt;
&lt;p&gt;(3) 这个任务他/她能收获什么？｜这个问题放在最后，但是是最重要的一项。组员一般是本科生，那么就应该思考除了成果的挂名之外，还能不能收获一些技术上/其他能力上的成长。&lt;/p&gt;
&lt;p&gt;今天和Stacey讨论这个问题的时候，我又有了一点新的看法。她说她一直带不好团队，她的协作总是不愉快，导致她总是很难过，怀疑自己。我告诉她这和她这个人一点关系都没有，也和她解释了协作不愉快的原因。&lt;/p&gt;
&lt;p&gt;期间我说了一句话：在学校里当PM，让团队成员各司其职不是一项任务，而是一个&lt;strong&gt;目的，&lt;strong&gt;一个&lt;/strong&gt;结果&lt;/strong&gt;，怎么理解这句话呢？在学校参与项目，很容易碰到摆子，更可恨的是他完成了你布置的任务，表面上各司其职了，但是完成任务的质量非常糟糕，到头来还得PM自己来擦屁股，（但是我遇到的组员都很棒，我爱他们！）。那么各司其职这时候就不是要关注的点了，而如何让每个人都更多地付出，更好地各司其职才是PM要做的，因此各司其职是一个我们追求的结果，管理的目的目标，而不是项任务。&lt;/p&gt;
&lt;h5&gt;避免压力向下蔓延&lt;/h5&gt;
&lt;p&gt;承担部分压力，并且避免压力向下蔓延是一个PM该做的，这是我的切身体会。如果一个PM一挨批，就对着其他人发火。一有紧急任务自己不排期直接定个ddl让所有人拼老命往前赶，那这个团队出来的成果也是糟糕且充满戾气的。&lt;/p&gt;
&lt;h5&gt;责任&lt;/h5&gt;
&lt;p&gt;本文不想吐槽任何人，但是没有责任感，遇到困难就踢皮球，遇到成果就抢着要，干活不见人，催人第一名的PM，反面教材。&lt;/p&gt;
&lt;h3&gt;Outro&lt;/h3&gt;
&lt;p&gt;希望自己能早日成长为一个好的leader，并且有朝一日能够拥有一款属于自己的好产品，加油🚀&lt;/p&gt;</content:encoded></item><item><title>聊聊开源社区的开发者成功</title><link>https://luckycc.cc/thoughts/4_devsuccess/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/4_devsuccess/</guid><pubDate>Fri, 30 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;G6这两天发布Beta版本了，和G6的缘分非常奇妙，从五月份逛他们官网帮她们修了几个typo，到后面偶然撞见发布了issue hunt悬赏令随手接了一个，直到今天已经陆陆续续合入了&lt;strong&gt;22&lt;/strong&gt;个PR了。G6是我第一个参与的开源社区，一个来自蚂蚁AntV的图可视化开源项目，写篇水文记录一下在做开源过程中的一些感悟。&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;五月底，第一个项目已经步入了落地收尾阶段，马上要开启第二个子项目。这个项目听起来非常难，大致内容是做一个渐进式增量布局，用户在点击一个节点时，要将这个节点变为一个凸包，包裹着这个节点底下的子节点，凸包包裹着凸包。即包含了增量布局技术，又需要支持无限的层级下钻。甲方让我们基于svg给一个解决方案，这无疑在布局和渲染上都面临巨大的挑战。&lt;/p&gt;
&lt;p&gt;搜集资料阶段，逛G6官网发现了G6有一个叫Combo的可视化方案挺符合我们的要求的，初步玩了一下他们的demo，就知道我们要找的就是这个。但是因为一些懂得都懂的原因，我们不能直接用G6，而且G6 v4的ComboLayout在大规模点边图上容易出现重叠。在浏览官网的过程中，发现了几处demo有404 not found error。于是我想着哎这好修，那我给他们修一修吧！&lt;/p&gt;
&lt;p&gt;因为是第一次提PR，在敲commit信息的时候我非常谨慎，在写PR信息的时候我也非常谨慎，认认真真检查每一个单词，还检查了语法什么的，最后提交。一个修复文档的PR花了我一晚上。&lt;/p&gt;
&lt;p&gt;第二天，PR被合入，作者在我的PR下评论：非常感谢你的PR。我很兴奋，因为我在目前国内大型的可视化开源框架里有了我的第一条commit。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;非常机缘巧合，过了几天，G6推出了Issue Hunt计划，帮助他们完成功能的迁移和一些新Feature，就能够得到奖金。&lt;/p&gt;
&lt;p&gt;起先我不敢认领，因为之前从没参与过开源工作，也很害怕耽误事儿，自己完不成任务怎么办。但是我挺想做的，首先是想参与一下开源，其次是G6是图可视化领域非常有名的库，我的硕士研究方向就是图可视化，而且开源经历对于学生来说还蛮宝贵的，以后也很想进入蚂蚁的可视化团队工作。&lt;/p&gt;
&lt;p&gt;前3个Issue，我都是先做的差不多了，再写认领信息，然后认认真真地把PR写好，commit信息规规整整的写好，提交。好在有一些canvas开发基础，上手也比较快，写起来效率很高也很得劲。&lt;/p&gt;
&lt;p&gt;到后面因为我写了很多Item迁移的工作，觉得和简单写起来也很快，于是就把这部分工作个人包圆了。在写的期间帮忙修了一些小bug，最后在Beta发布前几天还帮忙把Item部分的函数注释完善了下，生成API文档。&lt;/p&gt;
&lt;h2&gt;Dev Success&lt;/h2&gt;
&lt;p&gt;Dev Success，我译为开发者成就感，这是我认为开源社区作者需要思考的一个点。意思是在维护一个开源社区的时候如何让开发者借助你的项目成功，也就是吸引开发者来贡献代码吧。&lt;/p&gt;
&lt;p&gt;我觉得G6的Issue Hunt就挺好的，至少对于我来说挺好的，既能参加开源项目，还有点小零花拿，还能认识行业的前辈，真好～！&lt;/p&gt;
&lt;p&gt;但是当然不是所有开源项目都应该拿钱来吸引人了，这里想了想其他的办法（意思是我有开源项目，我会这么做：）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PR评论区/社交媒体上的感谢，对我来说收到一位敬重的行业前辈的一句感谢会挺开心的【成本：低】&lt;/li&gt;
&lt;li&gt;评选年度最佳贡献开发者，邮寄一些周边。这点似乎许多社区都会做【成本：中】&lt;/li&gt;
&lt;li&gt;【成本：高】的不建议，不建议在Dev Success上花费太多，因为这似乎有点违背开源精神&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;羊腿学习法与Contributor学习法&lt;/h2&gt;
&lt;p&gt;羊腿学习法来自大圣：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;我还有一个Contributor学习法：如果想要在社区里找一个师兄，可以先帮他的项目提一些PR，做一些贡献。等到有一定的存在感了，约一顿饭就显得没有那么奇怪了。如下：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</content:encoded></item><item><title>关于主动汇报</title><link>https://luckycc.cc/thoughts/5_activereport/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/5_activereport/</guid><pubDate>Mon, 10 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;主动向领导报告你的工作进度，和工作成果。并且能够定期有个反馈是非常重要的。&lt;/p&gt;
&lt;p&gt;至少自从我养成主动报告的习惯后，再也没被导师骂过了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主动报告的0%，要优于被动报告的50%&lt;/strong&gt;。意思是如果你做了一个80分的工作，主动向上级汇报会有120分的效果，并且能及时得到反馈。如果等老板来找，可能只能有60分甚至更低的效果。&lt;/p&gt;
&lt;p&gt;校企合作和客户沟通更是如此，做产品也是如此，WIP的功能要及时在社媒上发布。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当让你的客户等待的时候，他就已经在思考你的缺点了&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title>可视化系统的价值</title><link>https://luckycc.cc/thoughts/6_vissystemvalue/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/6_vissystemvalue/</guid><pubDate>Sun, 01 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;可视化其实只是让数据更加易读，但在帮助用户理解数据，挖掘数据上却收效甚微，甚至在一些特定场景下几乎没有帮助。这是因为如果用户要理解一份数据，前提是拥有这份数据的上下文及相关的专业知识。举个例子，如果一份数据资产相关的炫酷可视化大屏摆在我面前，我可能也会一脸懵逼，因为我对数据资产的相关知识没有概念，根本没有从中挖掘数据特征与关联的能力。那么是否可以得出，可视化只能够帮助了解相关知识的人更加理解一份数据，而无法帮助不了解相关知识的人理解这份数据的结论。那么我曾经在项目的过程中，一直追求的（让用户理解数据）是不正确的，应该修改为：让用户&lt;strong&gt;更加&lt;/strong&gt;理解数据。&lt;/p&gt;
&lt;p&gt;但是可视化仍然应该遵循人因工程中的一些基本范式，在可视化设计中仍需遵循特定的美学度量，如：节点避免重叠，连边避免交叉等。在交互的时候要考虑用户的心智，mental map的保持等。&lt;/p&gt;</content:encoded></item><item><title>聊聊心理学中的“投射效应”</title><link>https://luckycc.cc/thoughts/7_projectioneffect/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/7_projectioneffect/</guid><pubDate>Thu, 02 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这两天看到一篇文章：在大厂中评绩效的时候一个非常有意思的现象：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一个人他打出去的评价，往往在自己身上也会出现&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这里Quote下原文吧&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;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></item><item><title>2023-自省思考与成长</title><link>https://luckycc.cc/thoughts/8_2023recap/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/8_2023recap/</guid><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;或许是因为不再封控的第一年，今年好多朋友都开始写起年终总结。早在12月的最后一周，我就想起笔记录这一年。但可惜做事太拖沓，跨年夜也不想动笔。直到24年的第二天，我才静下心来，开始这篇文章的写作。这或许是我去年一整年的一个缩影，计划了许多事情，但能坚持下来的却很少。&lt;/p&gt;
&lt;p&gt;2022年的12月，所有的核酸点在几天之内被清除，学校内也星星点点开始出现病例。每天都被淹没在“某宿舍阳了一个”，“对面实验室阳了一个”的消息中。我在学校出通知之前买了最早的机票，对象从上海出发，和我一起去海南旅游度假。我们甚至在年末还一起去看了薛之谦海口站的演唱会，都没有感染。我们都以为2023年，幸运的一年就这么开始了。&lt;/p&gt;

&lt;h3&gt;壹=&amp;gt;年初的一次打击&lt;/h3&gt;
&lt;p&gt;在今年的三月份，我经历了一场家庭变故。亲人的突然离世让我明白了两件事情：第一，世上除了生死之外再无大事，没有什么比自己的身体更重要；第二，人的生命不仅属于自己，第二点我相信经历过黑发人离世的朋友应该能体会到我在说什么。在收拾了近一个月的心情后，我开始重新恢复规律健身，更加注意自己的身体变化，以及和家里人的联系更加频繁了。&lt;/p&gt;
&lt;h3&gt;贰=&amp;gt;股四肌腱炎康复&lt;/h3&gt;
&lt;p&gt;今年的四月份，在一场校3v3篮球赛中，我们队被博士队淘汰，止步八强。赛后，我开始治疗困扰我已久的“股四头肌肌腱炎“。这其实不算是个很严重的伤病，但是会非常影响运动体验与运动表现。具体来说：在我每一次深蹲、上楼梯、之后，我的左腿外侧靠近膝盖的末端会有一种被针扎的感觉。造成这个伤病的根因是我的运动习惯比较差，在每一次训练完后不及时进行放松与拉伸，导致腿部的某个部分肌肉紧张，发力时肌腱代偿非常严重，发炎了。我花了3500大米在长沙找了一家康复机构，进行了为期一个月（共10次）的康复。&lt;strong&gt;感谢我的康复师刘Sir&lt;/strong&gt;，最终帮我彻底解决了这个伤病，也纠正了我的力线，教会了我很多运动康复的知识。而我刚好自己也对这方面的知识感兴趣，现在有朋友腿疼膝盖疼，我也可以稍微帮忙诊断并给出一些康复建议了。这钱说实话花得挺值的。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;叁=&amp;gt;Twitter、Github与博客&lt;/h3&gt;
&lt;p&gt;在四月份目睹了实验室学长学姐找暑期实习的不易后，我开始刷LeetCode了。但是很遗憾，坚持了200题后，我没有再继续了，一部分原因是我开始偷懒，另一部分原因是我接触了开源社区。&lt;/p&gt;

&lt;p&gt;受一些JD上的加分项“有开源经历与技术博客的优先”的影响。我开始接触开源社区，运营博客，Twitter（X）和Github。这件事的初心是蛮功利的，我希望之后在找工作的时候面试官能从多个维度了解我做的东西，让他知道我对技术的热情。同时也没那么功利，因为我想结交一些技术圈的朋友，我觉得他们很有趣，我想认识他们。我还想拓宽一下我的技术视野，刚学前端的第一年，我真的太菜了。在Twitter（X）上认识了很多非常非常有趣的人，因为真的太多了我就不全列出来了。&lt;strong&gt;真的很感谢每一个在推上互动过的推友&lt;/strong&gt;，给我的生活平添了好多快乐。&lt;/p&gt;
&lt;h3&gt;叁=&amp;gt;我的一些开源小玩具&lt;/h3&gt;
&lt;h4&gt;leetag&lt;/h4&gt;
&lt;p&gt;我的第一个开源小玩具是一个名叫&lt;a href=&quot;https://github.com/zqqcee/leetag&quot;&gt;Leetag&lt;/a&gt;的东西，这是我在刷题时想到的一个点子。在刷题的时候，有时候我并不需要看答案，而是需要一个小点拨，告诉我这道题应该用什么方法，我就可以做出来。所以我开发了一个Chrome插件，把隐藏在网页角落的题目提示与标签，用另外一种UI提示在题目顶端。另外，还可以提示这道题被Leetcode的哪场周赛收录，可以根据这个信息判断这道题是否热门。&lt;/p&gt;
&lt;p&gt;感谢&lt;strong&gt;川哥@Shenqingchuan&lt;/strong&gt;，我在川哥的一次直播中邀请川哥对Leetag进行直播探店。川哥给了我很大的鼓励，并且夸了我Readme写的蛮不错的（当天发了条微博，如图），保护了我一个菜鸟对技术的热情。后来我们也成功线下面基，成为了朋友。&lt;/p&gt;

&lt;h4&gt;ipad-cursor-hexo&lt;/h4&gt;
&lt;p&gt;我的第二个开源小玩具是&lt;a href=&quot;https://github.com/zqqcee/ipad-cursor-hexo&quot;&gt;ipad-cursor-hexo&lt;/a&gt;。这是我在捣鼓自己博客时候的一个点子。我在寻找一些美化自己博客的方法，于是找到了由@CatsJuice老哥（推上似乎都叫徐哥）开发的&lt;a href=&quot;https://github.com/CatsJuice/ipad-cursor/blob/main/README.md&quot;&gt;ipad-cursor&lt;/a&gt;。并自己写了一个非常简陋的胶水层把它应用到了我的hexo博客中，效果我非常满意。起初我担心这样写一个胶水层作为自己的项目会不会不太好，不是很礼貌，应该给别人提一个PR。但是徐哥居然把我写的博客放到了项目的README里，并且还发了推感谢我。这让我的推特涨了几十个粉丝，&lt;strong&gt;感谢@CatsJuice&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;到这里，我发现写这些小玩具涨Star是真的慢，身边的朋友都被我骚扰烦了，我像一个star乞丐，每天都问他们能不能给我点个star，能不能给我的github点个fo，这感觉太差劲了。老天看我太迷茫，给了我一个绝佳的机会。&lt;/p&gt;
&lt;h3&gt;肆=&amp;gt;我给图可视化界一哥AntV-G6做贡献啦&lt;/h3&gt;
&lt;p&gt;蚂蚁金服有一个团队叫AntV，AntV有一个图可视化框架叫G6，这似乎是国内Top级别的图可视化框架，每一个在做图可视化技术的人，都把它当作自己的比对竞品，我参与的校企合作项目的那个公司也不例外。我无数次幻想我有一天能够在这个框架中留下自己的代码，甚至进AntV工作。在查阅G6文档时，我发现介绍combo那一章有几处笔误，这几处笔误会导致某个链接无法跳转，产生404 Not Found错误。于是我开始看怎么给别人提PR，于是有了我人生中的第一个PR：&lt;a href=&quot;https://github.com/antvis/G6/pull/4554&quot;&gt;Fix issue#4552, another 404 not found and typo errors #4554&lt;/a&gt;。一个非常简单的PR，我收获到了当时的负责人十吾的感谢：&lt;/p&gt;
&lt;p&gt;这让我开心了整整一天，尽管这个PR非常简单，仅仅修复了文档上的typo。&lt;/p&gt;
&lt;p&gt;运气非常好，在提交这个PR后的没几天，G6开始推出了Issue-hunter，赏金猎人活动。在Github上发布任务，完成任务后能够获得对应的悬赏。因为校企项目非常忙，我每天抽了午休，晚饭后消食，以及睡前的2小时来进行对应任务的开发。因为我实在是对自己没什么自信，也没有参与过大型开源项目的贡献。所以我一般都是先实现某个功能，再去认领它。&lt;/p&gt;
&lt;p&gt;刚认领第一个需求的时候，我发了一条微博：“我真的能成为G6的贡献者吗，这是我想都不敢想的”。&lt;/p&gt;

&lt;p&gt;过了4天，我完成了我认领的第一条任务，矩形的迁移。在合入主分支后，我兴奋了两天。在那条任务之后，我大概熟悉了开发流程，和G6的部分代码，于是开始火力全开，疯狂认领任务。到了年底，我算了一下，我给G6提了将近30个大大小小的PR，其中不仅包含赏金计划，还包含了一些小的改动、API文档等。&lt;/p&gt;
&lt;p&gt;在给G6贡献了四个月后，我借着校企项目出差的机会，约十吾和G6VP负责人山果吃了个饭。饭后，我发了一条“Contributor学习法的推：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;给G6 贡献了四个月，终于有机会来杭州和两位图可视化领域的前辈吃个饭，收获到了很多很棒的建议，也证实了我的contribution学习法是work的 （contribution学习法：灵感来源于大圣老师的羊腿学习法，指如果想在社区里找一位导师或前辈，多结交朋友，可以给他们的仓库做一点贡献，然后约个饭）&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这条推让我的Followers涨了300个。&lt;/p&gt;
&lt;p&gt;年底，我因为是G6外部贡献量第一，并且似乎远超了第二名，收获了AntV颁发的开源之星证书。十吾和山果跳槽去了阿里，在新的赛道上继续精彩。&lt;/p&gt;
&lt;p&gt;**真的非常非常感谢十吾和山果，你们让我感受到了开源社区的温暖。**这或许也是我逢人必推荐G6，并且请求他们给G6点点star的原因之一。&lt;/p&gt;
&lt;h3&gt;伍=&amp;gt;在尝试了团队管理过后，我有了些新的体会&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;（这一段可能没有相关背景的人看不太明白，因为我暂时不想展开聊这个故事了）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2022年7月起，因为项目组内的一位32岁大龄博士学长想要赶紧毕业，于是我代替他成为了校企合作项目的组长，除了写代码之外，我还承担了一些团队管理，协调沟通的工作。在多方努力下，这个校企合作项目平稳运行了一年，终于在今年的7月开始出现一些我无法处理的矛盾。参加过校企合作项目的其实都懂，学生会被当成牛马一样疯狂压榨。我不想在年终总结里花太多篇幅来讲这件事情，不过在这件事情过后我明白了几件事情，也让我对一些观点有了全新的体会。&lt;/p&gt;
&lt;p&gt;首先，校企合作的学生侧项目管理真的不是一件简单的事情，一个组长要负责的事情真的挺多的：需要排期，跟进每一个同学的进度，和导师汇报工作，和企业方汇报工作。由于我们的项目比较特殊，一共有三方参与合作，因此协调与汇报的工作就变得十分复杂与繁琐。我的沟通能力，汇报能力得到了非常大的锻炼与提升。但是代码能力在其中没怎么提升，这是一个非常遗憾的点。好在我参与了开源项目，在另外一个方面，也补齐了代码能力这点短板。&lt;/p&gt;
&lt;p&gt;其次，太在意人情只会陷入无限的内耗。因为说是组长，实际上组内的同学和我一样都是学生。在分配任务的过程中我需要考虑所有人的意愿，也无法说出那种特别恶心的话来压力别人达到推进进度的目的，更何况人还是不太一样，良心让我真的做不出这种事情。&lt;/p&gt;
&lt;p&gt;最后，把情绪带入工作中是一件非常幼稚的事情。真的，这可以说是我2023年最大的收获。后续因为一些原因，我和项目经理互相不对付，加上我心态上的失衡，我不再负责项目组管理的工作。&lt;/p&gt;
&lt;p&gt;那位32岁的博士不得不重新开始管理项目组。或许是对我有些怨气，他先是让其他人不要和我过多沟通，说不要被我影响，然后告诉导师，和组内的同学，说我不专心做项目，在外面偷偷做开源。在生气了好几天之后，我也看开了。&lt;code&gt;&amp;lt;u&amp;gt;&lt;/code&gt;成年人的世界里不做改变，只做选择 &lt;code&gt;&amp;lt;/u&amp;gt;&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;陆=&amp;gt;2023精选回忆碎片&lt;/h3&gt;
&lt;p&gt;在2023年下旬，我新增了博客的Talks专栏，专门放一些我无聊的思考与总结。可惜我书看的不多，写不出什么好东西。&lt;/p&gt;
&lt;p&gt;今年的朋友圈发的没那么频繁了，不是生活更无趣了，而是自己的情绪更稳定了。&lt;/p&gt;
&lt;p&gt;五月份回武汉送我的好兄弟张书豪毕业了，见了很多好朋友，我知道这次分别过后再见可能就真的难了。我开始怀念大四那年一起坐在南湖边吹风野餐的日子。&lt;/p&gt;
&lt;p&gt;七月份项目出差，**感谢我的好兄弟钰姐和范宝，**在我最崩溃的时刻你们给了我很大的帮助，无论是物质还是精神层面。&lt;/p&gt;
&lt;p&gt;十月份去上海见了我的偶像，开源魔法师Anthony Fu，十一月份在长沙见了羊腿学习法创始人花果山大圣。&lt;/p&gt;
&lt;p&gt;年底推特达到了600+fo，顶着AntV G6贡献者的名号，在推上认识了好多人，也面基了一些推友，交到了一些很棒的朋友（特指哪几个我就不在这里点名了哈～&lt;strong&gt;感谢你们，我们一起度过了2023年的最后一天&lt;/strong&gt;）。&lt;/p&gt;
&lt;p&gt;参加了Cali举办的Zolplay第一节开发者交流会，&lt;strong&gt;感谢Cali，我见过最酷的老板&lt;/strong&gt;。在交流会上我见到了Randy。在那次交流会后，我决定取我的codename &lt;code&gt;zqqcee&lt;/code&gt;的最后三位字母&lt;strong&gt;Cee&lt;/strong&gt;作为我的昵称。他的读音和字母“C”是一样的。本科的时候，大家叫我超超，所以我一般用这个名字来介绍自己。我在交流会后说我想当个技术网红，可能是当时刚刚接触开源社区，很羡慕那种可以到处做演讲的大佬，我觉得太酷了。但是过了几个月后我感觉我成为这种人的可能性有点小，因为自己的技术水平有点太菜了，在干一年前端试试看会不会有改观。&lt;/p&gt;
&lt;p&gt;因为一些特殊的原因，有一些事情不太方便写在这篇年终总结里，&lt;strong&gt;感谢Perkin，@Immmmmm赖哥，如果你们看到了的话应该会知道我为什么感谢&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;感谢Kaiyi&lt;/strong&gt;，我需要单独感谢一下你。在我最迷茫的时候给我提供了很多非常棒的建议，我们或许有着类似的经历，感谢你接受了我的咨询并让我重新振作起来。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;感谢我的女朋友Stacey女士&lt;/strong&gt;，在受陀思妥耶夫斯基的一句“爱具体的人，而非抽象的人”的影响下，我们又合作了一年。24年希望我们都能收获理想的offer，共同走向美好的未来。&lt;/p&gt;
&lt;h3&gt;柒=&amp;gt;2024的一些小愿景&lt;/h3&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;能够养成看书的习惯（不过这是我2022年的愿景），然后多思考&lt;/li&gt;
&lt;li&gt;今年希望能看到自己的腹肌，可以不用太明显，但是我想知道它是不是对称的&lt;/li&gt;
&lt;li&gt;保持一个健康的身体，健身规律一些&lt;/li&gt;
&lt;li&gt;多打电话给奶奶&lt;/li&gt;
&lt;li&gt;和更多的推友面基，认识更多的人（特别是和Simon哥面基，他刚刚评论了我的推特，我必须在这里给他点出来）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2024是本命年，也是我要开始秋招，半只脚踏入社会的一年，祝福自己一切顺利。&lt;/p&gt;
&lt;p&gt;有点困了，感谢看到这里的你，同样祝你一切顺利，2024一起加油～！&lt;/p&gt;</content:encoded></item><item><title>聚光灯效应</title><link>https://luckycc.cc/thoughts/9_spotlight/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/9_spotlight/</guid><pubDate>Mon, 29 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&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;strong&gt;曝光给更多的周围的同学&lt;/strong&gt; 上下游去使用 去体验 去寻找更多的潜在用户 导致最终的结果 并没有达到他自己的预期 而&lt;strong&gt;变成了一种自high&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以聚光灯效应 &lt;strong&gt;其实深刻的提醒我们 要拿到的结果 或者成绩 他一定只能是更客观的事实 而需要减少自己更多的主观判断&lt;/strong&gt; 另外放心大胆的去做事 其实真正关心你做的事情的人 只有你自己 在拿到结果的时候 也要更主动的让你的老板 你的上下游 切身体会到你做的事情的价值&lt;/p&gt;</content:encoded></item><item><title>前端秋招指北</title><link>https://luckycc.cc/thoughts/i1_interview_experience/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/i1_interview_experience/</guid><description>昨晚完成了自己秋招很可能是最后一场面试，作为2025届秋招人，谈谈今年秋招的个人战况与感受。可能引起不适，反胃，请辩证看待，欢迎讨论。</description><pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;个人背景介绍&lt;/h1&gt;
&lt;p&gt;财经类院校211本，985硕，专业均为CS科班。&lt;/p&gt;
&lt;p&gt;简历上写了三个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实习经历：&lt;strong&gt;1段&lt;/strong&gt;含金量还可以的实习&lt;/li&gt;
&lt;li&gt;开源经历：&lt;strong&gt;1段&lt;/strong&gt;开源经历&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2段&lt;/strong&gt;加起来一年半，可写在简历上的实验室项目&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;求职意向：web前端开发&lt;/p&gt;
&lt;p&gt;投的公司：所有国内头部互联网大厂（除了某睡觉厂）&lt;/p&gt;
&lt;p&gt;战果：14投10面，3挂，&lt;strong&gt;6意向&lt;/strong&gt;，&lt;strong&gt;1排序等待回复中&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;秋招时间线&lt;/h1&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1-2月：系统刷题，准备八股&lt;/li&gt;
&lt;li&gt;3-4月：找暑期实习，高强度面试。以赛代练，培养自己的面试体感与个人表达&lt;/li&gt;
&lt;li&gt;6月：大厂开放算法岗提前批，bg较好的算法岗的同学可以着重关注，一般以“xx计划”命名&lt;/li&gt;
&lt;li&gt;7月：陆续开放秋招正式批，高强度进行笔试与测评&lt;/li&gt;
&lt;li&gt;8～10月：秋招正式批面试开始，进行高强度面试&lt;/li&gt;
&lt;li&gt;10月中旬：陆续发放意向书&lt;/li&gt;
&lt;li&gt;11～12月：谈薪，沟通并发放正式offer&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;面试准备&lt;/h1&gt;
&lt;p&gt;五个部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1 自我介绍。按照简历上划分的三个部分，进行面试内容的准备（实习，开源，项目）&lt;/li&gt;
&lt;li&gt;2 自我介绍中提到的项目内容&lt;/li&gt;
&lt;li&gt;3 前端相关八股（JS/CSS/HTML/网络/前端框架(React)/浏览器知识/手撕题等，具体内容整理在博客的“秋招专栏”中）&lt;/li&gt;
&lt;li&gt;4 常见面试问题（设计模式/场景题/HR面等）&lt;/li&gt;
&lt;li&gt;5 算法笔试/面试&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;自我介绍&lt;/h2&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;实习经历：挑选了三个自己独立推动落地的事情进行分享，并严格按照“背景-解决-收益”的SOP进行介绍。&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;自我介绍环节我会花费5分钟的时间，和其他秋招的同学聊过，因为他们害怕被面试官打断，所以自我介绍火急火燎一分钟就结束了。&lt;/p&gt;
&lt;p&gt;我不是很喜欢这种介绍方式，&lt;strong&gt;总觉得应该尽可能地给面试官更多context，让他有更多的问题可以在下一个环节与我讨论&lt;/strong&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;/p&gt;
&lt;p&gt;八股是我的弱项，因此我花了大多数时间准备八股。方法如下：&lt;/p&gt;
&lt;h3&gt;📖 错题本&lt;/h3&gt;
&lt;p&gt;和高中时期提升自己成绩的常用方法一样。在每一次面试后，将自己没有回答清楚的问题记录下来。我为此还在Notion中维护了一个错题本，记录自己回答错误甚至没有见过的问题。目前已整理至 &lt;a href=&quot;https://luckycc.cc/interview/&quot;&gt;秋招专栏&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;随着面试经历的增多，我将前端的八股分为了以下几类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;li&gt;Javascript基础&lt;/li&gt;
&lt;li&gt;React相关知识&lt;/li&gt;
&lt;li&gt;浏览器相关知识&lt;/li&gt;
&lt;li&gt;前端工程化相关（性能优化相关也归类于此）&lt;/li&gt;
&lt;li&gt;手撕题（如实现Promise，实现防抖节流等）&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;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;当然这也有缺点，如果没有任何准备进入面试，面评容易花。我在腾讯的人才库中反复沉浮，经历10次“被捞起-被挂”后，秋招流程彻底结束。在知道腾讯上岸无望后，我把腾讯的面试当作自己的练兵场，积累面试经验。&lt;/p&gt;
&lt;p&gt;当然，如果没有面试邀约的话，还有一个方法是去牛客上找大家的面经来看，然后找到自己不会回答的问题尝试回答。一定要口述答案，不要只在心里简单默念。&lt;/p&gt;
&lt;h3&gt;⛰️ 日常积累&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;我将自己的阅读读物替换为了《React设计原理》，《你不知道的JavaScript》等书籍，高强度刷知乎，掘金等平台。&lt;/p&gt;
&lt;p&gt;这个方法获取八股知识的效率比较低，但是也有好处。对于我来说，这么做能够把“准备八股”变成一件没有那么无聊的事情，也增加了一些面试时与面试官的”&lt;strong&gt;谈资&lt;/strong&gt;“。&lt;/p&gt;
&lt;h3&gt;💡 回答八股的小技巧&lt;/h3&gt;
&lt;h4&gt;“交流”的心态&lt;/h4&gt;
&lt;p&gt;在回答面试官八股问题的时候，尽量不要用背诵的方式来回答问题，尽管这个问题你已经回答过无数次。&lt;/p&gt;
&lt;p&gt;可以想象着自己在和面试官正在交流/讨论这一项技术，甚至当做对面试官的教学。&lt;/p&gt;
&lt;p&gt;举个例子，一个很常见的前端八股：你是如何理解JS事件循环的？&lt;/p&gt;
&lt;p&gt;这个问题就可以从 JS 单线程，无法实现异步，事件循环的出现让 JS 拥有了异步能力开始讲起，而不用上来就开始介绍宏任务和微任务等等。
当然对于这个问题，还有一些比较加分的回答，比如你可以告诉面试官，由于现在任务的优先级粒度划分得更细，V8 已经不仅仅区分宏任务和微任务了，而有了更细粒度的任务划分。&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;strong&gt;探索候选人知识边界的过程&lt;/strong&gt;，毕竟你只是一个校招同学，有不了解的知识非常正常，放轻松。&lt;/p&gt;
&lt;p&gt;还有一种做法，是&lt;strong&gt;让面试官提供一些提示&lt;/strong&gt;，这种做法常用于算法/代码题没有思路的场景。&lt;/p&gt;
&lt;p&gt;我曾经在一场面试中通过面试官的提示，手撕了一个带有重试功能的 &lt;code&gt;Promise&lt;/code&gt;，写了半个小时，但也没有影响我在这场面试中获得一个不错的评价，面试说和我沟通感觉起来感觉很流畅。&lt;/p&gt;
&lt;p&gt;其实在面试过程中，候选人的“思考方式“与”沟通能力“是两个非常重要的考察因素。so feel free to ask&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;p&gt;其实对于前端来说，笔试&lt;strong&gt;并不是最重要的参考因素&lt;/strong&gt;，leetcode &lt;strong&gt;200题&lt;/strong&gt;基本够用。笔试的成绩够用即可，在录用时做个参考。&lt;/p&gt;
&lt;p&gt;值得注意的是，算法笔试OJ通常采用 &lt;strong&gt;ACM模式&lt;/strong&gt; ，也就是需要编写输入输出。&lt;/p&gt;
&lt;p&gt;因为在23年3月目睹了同门师兄姐在他们那届秋招时笔试的严峻，在23年的上旬，我就已经按题型几乎刷过一遍热门的算法题了，当时选用的语言是Java，参考的刷题网站是 &lt;a href=&quot;https://labuladong.online/algo/&quot;&gt;labuladong的算法小抄（现在收费了）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;到了24年初，我开始了第二轮刷题，本次选用的语言是JavaScript，参考的刷题网站是&lt;a href=&quot;https://programmercarl.com/&quot;&gt;代码随想录（强推）&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;如果时间紧，建议重点刷下&lt;strong&gt;二叉树&lt;/strong&gt;和&lt;strong&gt;动态规划&lt;/strong&gt;两个章节，这两个章节在算法笔面试中常考。&lt;/p&gt;
&lt;h1&gt;实习&lt;/h1&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;尽可能让自己努力的方向与小组 OKR 方向平行。&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;strong&gt;额外的产出&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;除了保证日常需求的交付外，那些额外的产出才是证明你“能力”的内容。&lt;/p&gt;
&lt;p&gt;（1）对于实习转正，这些额外的产出能够：论证你作为一个校招生具备充足的&lt;strong&gt;主动性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;（2）对于秋招，这些项目能够增价简历的“厚度”，并帮助你回答一些“你在实习期间遇见过最有挑战的事情”，“分享一个实习期间你做过印象最深刻的项目”等问题。&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;strong&gt;单独沟通&lt;/strong&gt;和&lt;strong&gt;主动宣传&lt;/strong&gt;等方式，向他人分享一些值得分享的技术或正在做的事情。这么做有以下两个好处：&lt;/p&gt;
&lt;p&gt;（1）帮助你梳理清楚这件事情的背景，做法与价值；&lt;/p&gt;
&lt;p&gt;（2）寻找一些能够在不同小组之间横向达成合作的机会；&lt;/p&gt;
&lt;h1&gt;消息与资源获取&lt;/h1&gt;
&lt;p&gt;我很喜欢 &lt;a href=&quot;https://kaiyi.cool/&quot;&gt;kaiyi&lt;/a&gt; 的一句话：“不要把和你身边共同处于秋招阶段的伙伴当作敌人，你们的信息应该共享，真正应该被视为竞争对手的是那些你压根不认识的人”。&lt;/p&gt;
&lt;p&gt;秋招期间，人与人之间的信息差体现得淋漓尽致。包括秋招前的实习招聘信息的获取，秋招时组内的基本信息，HC情况等等。&lt;/p&gt;
&lt;p&gt;常用于信息获取的渠道比较常见了，这里就不列举了。&lt;/p&gt;
&lt;p&gt;引入一下我的 &lt;a href=&quot;https://x.com/zqqcee/status/1704012477670003172&quot;&gt;Contributor学习法&lt;/a&gt;：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;虽然我有点小丑，之前一直合作的负责人不再维护G6了，后来我去找新的负责人要内推码时，没有得到回复。但是我仍然推荐这种方式，能够去结识一些社区的大佬，说不定实习机会就藏在一封邮件中👀。&lt;/p&gt;
&lt;h1&gt;介绍一个项目&lt;/h1&gt;
&lt;p&gt;在面试中我们经常需要向面试官介绍一个项目，而这个面试官很有可能没有接触过类似的业务，甚至到了交叉面时，对方还可能属于不同的岗位。这意味着你需要给一个完全没有上下文与相关知识的人，甚至非技术的人，介绍你的技术项目。&lt;/p&gt;
&lt;p&gt;“和对方讲清楚一件事情”这个能力是我非常欠缺的。首先，我不了解结构化表达的方法，表达能力比较弱。其次，在表达时，我总是默认受众与我有等量的信息上下文，在介绍一个概念或事情时，我总是默认对方和我一样已经完全具备前置知识。&lt;/p&gt;
&lt;p&gt;一味介绍&lt;strong&gt;技术深度&lt;/strong&gt;的方式显然是不可采取的，我有几点小 Tips ，读者可以做个参考：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于比较重要的项目，遵循：”背景介绍 =&amp;gt; 问题定义 =&amp;gt; 问题提炼（分点） =&amp;gt; 解决方案（针对问题逐一解决） =&amp;gt; 取得的收益“的 SOP进行介绍&lt;/li&gt;
&lt;li&gt;对于不重要的项目，简单概括背景，做法与收益即可&lt;/li&gt;
&lt;li&gt;不要背诵，给面试官留几个提问的气口，沟通最好以一种对话的语气向前推进&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;战后调整与心理重建&lt;/h1&gt;
&lt;p&gt;临时增补的一个部分，主要想谈谈面试中遇到恶心人的面试官如何调整心态。&lt;/p&gt;
&lt;p&gt;作为“身经百战”的面试博主，自然也是阅官无数了，简单罗列几条面试官罪行：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;面试前迟到（40%）&lt;/li&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;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;p&gt;被PUA，被否定，明明面试表现感觉良好，但还是被挂了，&lt;strong&gt;请不要把原因完全归结于自己能力不足&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;面试不通过的原因可以有很多：公司文化不符、部门HC不足、面试官心情不好，甚至会因为你太优秀了怕你不接offer为理由，终止一次面试，你的能力只是数万条理由中的一条，所以不必在意。&lt;/p&gt;
&lt;p&gt;面试是一场恋爱，不要以一个卑劣的姿态去迎合对方的喜好，选择适合自己的团队和找到适合团队发展的候选人才是这场面试的主要目的。&lt;/p&gt;
&lt;h1&gt;Offer的选择&lt;/h1&gt;
&lt;p&gt;不同级别的薪酬中值通常是个等比序列，而不是等差。例如比你高一级的人可能工资比你多一半，但高三级的人不是比你多150%，而是多238%。在这个模型里，你需要优化你的五年后，或者十年后能到达的高度。所以在比较offer时，&lt;strong&gt;不需要太关心它们之间的数字差价，而是关心去你去了之后的发展&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;感悟&lt;/h1&gt;
&lt;p&gt;2024年11月12日，双十一刚过，秋招也已经接近尾声了。&lt;/p&gt;
&lt;p&gt;秋招是一场拉锯战，不仅考察候选者的能力，还挑战着他们的耐心，意志，心态。既然是战斗，那就应该以战斗的心态来面对秋招，比如提前了解即将面试的公司，做好战前储备与战后复盘，对自己的军备水平进行准确评估等等。&lt;/p&gt;
&lt;p&gt;秋招是我们和人才市场的一场博弈，我们希望通过拿到多收割意向来论证自己的能力，拥有更多选择，公司希望使用最低的成本，来招到最适合的人才。&lt;/p&gt;
&lt;p&gt;博弈游戏，筹码多的人先赢，先亮底牌的先输。祝顺利！&lt;/p&gt;</content:encoded></item><item><title>踏上新的旅程</title><link>https://luckycc.cc/thoughts/w1_the_new_journey/</link><guid isPermaLink="true">https://luckycc.cc/thoughts/w1_the_new_journey/</guid><pubDate>Mon, 09 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;聊聊租房&lt;/h2&gt;
&lt;p&gt;其实租房的计划很早就开始了，对象5月中旬上班，我们4月底开始找房子，5月初来杭州一起看房。&lt;/p&gt;
&lt;p&gt;租房的预算拉的比较高，在问完了一圈价格之后，把预算设定在了3500-4500这个区间内，户型2室一厅或3室一厅，根据房子的好坏进行动态增减。&lt;/p&gt;
&lt;p&gt;我们把工作后住的房子看的很重要，因为这直接关系着我们的日常生活体验，而日常生活体验又会反向地影响工作体验和工作表现。工作表现和体验也会反向影响日常生活体验。&lt;/p&gt;
&lt;p&gt;除了在公司的时间之外，我们很大一部分时间都会待在家里，所以家里是否温馨，居住是否舒服，都直接决定了上述的一切。其实房租的价格不超过月收入的30%是一个比较合理的区间，所以3500-4500对于两个人来说是一个没有什么压力的负担范围，加上公司还会补贴800房补。&lt;/p&gt;
&lt;p&gt;对于房间的规划我们也想好了，一个主卧一个次卧，如果有多的房间，就拿来做书房。我觉得家里有一个书房（电竞房/娱乐室）还是非常重要的，书房能够给我提供回家后处理一些紧急工作/早上浏览一些资讯和文章/周末打打游戏的空间和条件。&lt;/p&gt;
&lt;p&gt;非常幸运，我们最后以3700/月的价格租到了一个房东自住的三室一厅。户型各方面我都很满意，虽然家具有点丑，但是房间的装潢各方面做的还挺不错的。&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;2025.06.04，我开始工作了。虽然还没拿到毕业证，在人事系统上挂的也是实习生的标签。&lt;/p&gt;
&lt;p&gt;刚开始工作的前两天很不适应，平日里我的作息非常紊乱，有时候要熬夜到凌晨3/4点才能睡着，然后第二天直接一觉睡到中午。这种作息我早就想开始改变，但是缺乏一个契机，现在我开始工作了，这个契机来了。&lt;/p&gt;
&lt;p&gt;工作的第一天我躺在床上翻来覆去到4点才睡着，第二天9点就要起床去参加入职新人培训。一切的流程和23年年末我刚来实习的时候一样。中午和&lt;a href=&quot;https://hong97.ltd/about&quot;&gt;@丁弘&lt;/a&gt;在徐春娇吃了顿湘菜，下午就去工位报道了。&lt;/p&gt;
&lt;p&gt;先和我的ld做了一个简短的one-one，后和mentor再做了次one-one，简单沟通了一下最近两周的工作安排。&lt;/p&gt;
&lt;p&gt;周围的同事几乎没有什么变化，没什么需要过多相互介绍的。周围的一切都很熟悉，开通飞书后和之前实习认识的几个好哥们儿打了招呼，在工位上配了一下午环境，一天就过去了。&lt;/p&gt;
&lt;p&gt;在开始工作前，我很想摆脱读研的环境。等到真正要开始工作了，又开始忐忑了，做了很多悲观假设，希望自己能在工作上一切顺利吧，当然最重要的是身体和心理健健康康的，加油！&lt;/p&gt;
&lt;h2&gt;更高效摄入信息的方式&lt;/h2&gt;
&lt;p&gt;很久没上Follow了，错过了好多资讯，最近在扫一些比较优质的博客，所以需要摄入大量的信息。刚好想到前阵子找&lt;a href=&quot;https://x.com/TinsFox&quot;&gt;@热烤奶&lt;/a&gt;要了Dia邀请码，还没有体验，刚好趁这波机会体验一下。&lt;/p&gt;
&lt;p&gt;效果非常惊艳，总结的内容虽然比较简略，但是能够起到一个粗读浏览的作用。如果扫完一篇文章我比较感兴趣，我就会再细读一遍，看看有没有什么更加深入的观点。&lt;/p&gt;
&lt;p&gt;总而言之Dia大大提升了我摄入信息的效率，AI能够在摄入信息这个动作上前置地帮我过滤掉一些我并不是特别感兴趣的信息还挺不错的。可惜它并不支持自定义模型，现有的模型在总结信息上效果还可以，但是其他表现并不是特别理想，可能后续出付费版后会上这一个功能。&lt;/p&gt;
&lt;p&gt;关于这个功能，&lt;a href=&quot;https://lutaonan.com/&quot;&gt;@Randy&lt;/a&gt;做了一个&lt;a href=&quot;https://doc.sumbuddy.app/&quot;&gt;SumBuddy&lt;/a&gt;，可以配置自己的模型，定制助手，下次可以付费体验一下。就像他在产品页说的那样：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;市场上已经存在很多浏览器 AI 助手，但 SumBuddy 能满足一部分这些工具没有满足的用户。&lt;/p&gt;
&lt;p&gt;对于已经自己购买 AI 模型 API 的用户，不应该再为 AI 工具提供的模型再付费，这是我做 SumBuddy 的基本理念。不提供模型的劣势是不能开箱即用，你需要先配置你的 API Key 才能开始使用。但带来的好处是，你的对话不需要经过我们的服务器。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;思考记笔记的方式&lt;/h2&gt;
&lt;p&gt;在浏览一些文章/博客的时候，我通常因为某句话或某个观点太优质想要把他记录下来。但是经过很长一段时间后发现，这句话在被我记入备忘录后，他的价值就结束了。我不会在之后的任何一个时间打开他，甚至很难再想起他。&lt;/p&gt;
&lt;p&gt;在刷博客的时候发现 &lt;a href=&quot;https://notes.andymatuschak.org/%C2%A7Note-writing_systems?stackedNotes=zR6RRbCfY5rFkiimFnaJZKB&quot;&gt;Andy Matuschak&lt;/a&gt; 介绍了长青笔记的概念，和Randy分享的PARA记笔记法。觉得还是有很多可以借鉴的地方的，可以趁这个机会重塑一下自己的笔记流。&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;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;Reading&lt;/h2&gt;
&lt;h3&gt;如果你很忙，那你一定做错了什么&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://calnewport.com/if-youre-busy-youre-doing-something-wrong-the-surprisingly-relaxed-lives-of-elite-achievers/&quot;&gt;原文链接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;研究人员邀请了一组小提琴演奏家，参与一项实验。实验结果将这群人分为了Elite Player 和一组 Average Player，得出了以下三条结论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Average Player和 Elite Player的工作时长几乎一致&lt;/li&gt;
&lt;li&gt;Elite Player每日花在&lt;strong&gt;刻意练习专业技能&lt;/strong&gt;上的时间比Average Player多 3小时&lt;/li&gt;
&lt;li&gt;Average Player每天将自己的工作随意地分散在各个时刻，而Elite Player把工作时间集中在了早上和下午。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;文中有一个观点非常有价值：本质上，Hard work 和 Hard to do work 具有本质区别。Hard work并不是一件令人愉悦的事情，但其提供的**“技能可持续进步”**能够产生强烈的满足感和动力。 因此，虽然Hard work 很辛苦，但是不会让人感到筋疲力尽。&lt;/p&gt;
&lt;p&gt;而 Hard to work 则恰恰相反，过度的无效努力会让人感到精疲力尽。很容易陷入一种“虚假的忙碌”，感到疲惫和压力。&lt;/p&gt;
&lt;p&gt;然而如果你的目标是幸福，良好，不平凡的工作，那么忙碌和疲惫应该是你的敌人。&lt;/p&gt;
&lt;p&gt;**因此，如果长期感到压力大并且工作到很晚，那一定是做错了什么。**作者从这项工作中得出了一个简单又令人吃惊的结论：少做，但全神贯注，毫无保留地去做你该做的事情。当你完成时，去享受这天所有剩余时间。&lt;/p&gt;
&lt;h3&gt;换个环境为什么能提高工作效率&lt;/h3&gt;
&lt;p&gt;人类的大脑善于过滤掉多余的传入信息，但如果这些多余的信息与我们相关，它就很难被忽视。认知科学先驱 E. Colin Cherry 将此称为“鸡尾酒会问题“——指的是在嘈杂的派对上，&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;/ul&gt;
&lt;p&gt;这有助于解释为什么在熙熙攘攘的咖啡店里，人可以高效地工作几个小时。因为咖啡店是一个陌生的环境，人的注意力不会被陌生的信息吸引。白噪音也同理，而当你在相邻的对话中听到一个熟悉的话题出现时，你的注意力就会被劫持。从这个角度来看，家里的环境太熟悉，有着太多可以劫持注意力的物品。&lt;/p&gt;</content:encoded></item></channel></rss>