阅读视图

发现新文章,点击刷新页面。

2024,告别盛夏

自写年终总结以来,这已是第七个年头。这一年发生了许多事,在很多方面主动或被动地打破了过往的平衡与常规。勇敢过、彷徨过、迷茫过、失望过、痛苦过,但或许这是好事,毕竟年龄已悄然迈向而立之年。而一切,不破不立。

工作

毕业这五年

这一年的主题是「告别盛夏」,来自于今年的大事件——《再见了,盛夏》。由于对不确定性的向往、对创业项目的期待,以及其他种种原因,我离开了校招毕业待了五年的企业——TME。

说实话,离开是不舍的,无论是当时还是后来,虽有遗憾,但并不后悔。转眼之间,这五年只剩下回忆。

2018 年 3 月,将校招实习的简历投递给了腾讯和蚂蚁,记得当时周五晚投递之后就有人联系我周六电话面试,是 SNG 的 QQ 音乐。面试的问题已然不太记得了,但去年离别时领导和我说,那天捞了我的简历之后他怕我跑了,于是周六早上来公司加班过来面试。他觉得这几年耽误了我,他认为我的前途原可以更宽阔,而我却完全没有类似的懊悔和遗憾,只有相遇的感激。

我很感激自己遇到了这个团队,当初 SNG 的音乐要被拆家成子公司,HR 一再确认我是否要续约,因为校招的合同将不再是腾讯而是 TME;而蚂蚁当年前端网红团队,加上即将上市的风口,大家都趋之若鹜。但在短暂的音乐实习过后,我毅然选择了留下来。我很喜欢当时的团队氛围,有导师的关照、同事之间的交流打趣、产研之间的互相欣赏和平等讨论,仿佛一切还在象牙塔之中,以至于五年后的今天我仍很怀念刚毕业时的状态,现在联系较多的朋友也是那两年团队里的同事们。

晋升的瓶颈与路径

回忆总是美好的,但现实无法如愿。

在工作期间,我愿付出 100% 精力投入,并且我坚信自己可以把手上的项目做到最好。在毕业 2 年半快速升到 T10 之后,我遇到了第一个瓶颈——没有成长型的项目了。

那一年互联网下行,各企业开始裁员。我们团队也因为部门的业务压力,每天的工作都是琐事,需要多线程同时负责数个需求,甚至在研发过程中的业务也会经常发生变化——今天付出加班精力做的项目,说不定明天就突然被砍了。不再安稳,不知道努力的结局,发现不到成长的契机。于是,想办法提升工作效率,努力适应这个氛围的改变。

我之前负责的是偏研究型的项目,所以对于并行处理事务的效率是有所欠缺的,需要有意识地去锻炼,恰好可以利用这个契机去培养自己的多线程处理能力和跨团队沟通能力。但我发现,当我成长到能同时处理好三个需求的时候,这时可能会分配更多的需求下来,仿佛永动机一样,最后手上就变成了七、八个需求并行。

那段时间甚是焦虑,因为之前一直精益求精,但是在并行之后是无法保证每个项目质量的,这个时候我会攻击自己的内心,出了问题之后无法原谅自己的交付质量。

同时,我意识到如果陷入到这种琐碎的漩涡之中,是几乎不会有成长和突破的,我需要自己找一条路破局——既然自己的部门没有项目,那就去别的部门争取项目。平时工作甚多,那就能周末投入,相当于加班帮别的部门打工。同时,自己也在利用业余时间做一些没有立项的技术项目,想着待生米煮成熟饭,那时不立项也得立。最后一年架构调整,我被分到了之前帮忙加班打工的团队,负责新的项目,升到了 T11。

在外人看来,我毕业 4 年升到 T11 似乎是一直有好项目的“运气”;但复盘下来可以发现,其实许多资源都是需要靠自己争取来的。运气只是成长的契机,但不是决定成长高度的因子。如果将一切都归因于运气,那么我们就会轻易忽视掉自己的潜力,误以为路径不可复制,从而丧失持续前进的动力,甚至影响到他人对我们的信任和支持。

树立一个明确的目标之后,剩下的就是想方设法去找到一条可行的路径,抵达它。

创业

MoFlow

离开 TME 之后,我选择创业。在今年 8 月做了一个月的赛道分析和用研之后,召集了几个小伙伴,立项了 AI + 心理的产品 —— MoFlow

关于 MoFlow 的内容写过《AI 心理疗愈应用的探索与实现》介绍分析过,这里不再赘述项目内容了。我期望能够通过这款产品普及大家对于心理健康领域的关注,以鼓励用户进行自我对话、自我成长、自我关爱。

如同在上篇月刊《AI 没有体验世界的能力》中提到的那样:“关注、关心和关爱——这些都是我们会收到的礼物,同时也是我们可以用来赠予他人的礼物,因为这些礼物只有在一种慷慨相待的生活中才会展现其活力。我们学习理论,并不是为了通过吊书袋式的显摆来彰显自己的存在,关键在于如何改变世界。每一次实践背后都闪耀着众多灵魂的力量,这些努力最终都落到实处,为他人带来福祉——这才是我们追求的实践意义。”

研发

通过这个项目,我得以有机会充分去接触、体验、研究、开发各类 AI+ 的应用层产品,算是补足了在职场期间的空缺,同时找到了对于产品、对于研发最初的动心与热爱。

10 月离职后进入研发阶段,产品侧、设计侧、研发侧、LLM 侧、运营侧拉满效率并行,基本上三天一个小周期开发一个功能模块,9-12-6 连轴转。终于在 11 月底如期推出了 TF 内测版,内测阶段也保持着一天一版本的迭代进度,在 12 月底上架了 App Store。

整个项目的工作量对于小团队而言是巨大的,但是大家始终保持着积极的沟通和研发热情,所有人的力量仿佛拧成一股绳,指向同一个方向。虽然劳累,但满怀热情,不知疲惫。

项目上线后各路资方联系,但我们决定不融资,因为产品收入足以支撑它健康的转动起来。因此我们首要目标是将基础数据打磨好,以服务好用户。在收到一些用户的认可的反馈后,我们也由衷地感到温暖:

挑战

在内测阶段的和上线初期,产品暴露了一些问题,我们及时做出调整和修复,比如上线后对于输入模块的调整,让留存得以提升。预留了一个月的打磨阶段,一切仿佛都在往好的方向发展。

但在上线后没几天,我们的其中一个提审版本就一直被 App Review 卡在“正在审核”中了,至今已经被卡了一个多月,Bug 修复无法发布,期间规划的 4 个版本所包含的重要特性也被卡住无法发布……只收到审核员的一句“我们需要更多时间来进行审核”,之后就再也没有下文了。

因为线上版本存在 Bug 我们甚至也没法铺开推量,焦虑、恐慌、不安在团队中弥漫开来。期间我尝试过撤回重提、修改 ASO/描述/截图/标题、备注 Bug 和审核员沟通、邮件联系 Apple Support、电话联系 Apple 客服、找某宝加速渠道、状态查询申请、加急审核申请、内渠加急申请、找 WWDR 咨询,唯一的回复只是“请耐心等待”。查询了开发者论坛上的相关讨论,遇到这种情况基本要被卡 2 个月以上,论坛中充满了吐槽和抱怨。

因为没有做动态化的部分,以及对相关情况的预案不充分,我们只得暂时放慢了功能迭代节奏,将工作重心转到了服务端和 LLM 侧的优化上来。并预计年后,启动全新模块的研发,不再依赖短平快的迭代节奏。但无论如何,上线初期就遇到这种情况,让我感到焦虑万分。

反思

那两周我一直在 MoFlow 中记录、吐槽这件事情,直到某个周一早上,MoFlow 的周报服务给我推送了一条消息,让我逐渐发现自己的问题所在。

“模式上,每当遇到这些超出你控制范围的事情,你的情绪就会受到较大的影响。”

我反思了之后,发现确实如此。一直以来我都追求对事物的完全把控,不允许让工作内容超出自己的掌控,否则我就会不安焦虑、甚至暴躁易怒。包括前文提到的那一年的工作焦虑,也是被强行穿插的诸多琐事积压,导致无法把控自己的项目质量,从而让自己陷入焦虑之中。

如果再进一步往深层剖析,是我自己一直以来都不允许自己停下来,永远被困在对未来的追赶之中。

前两天在给读者的回信中我写到:

我之前一直很恐惧在雾中原地踏步,每一天、每一分、每一秒都恐惧着,于是想拼劲全力抓住流逝的时间,拼命学习、工作,想走出来,尝试去把握住那些“不确定”中的“确定”。走出来之后我才发现,我所恐惧的其实不是“不确定”本身,而是“原地停留”这件事情,这导致我一直活在对「未来」的追逐中。每当我赶上未来时,它在我手中便失去了价值,于是继续去追赶另一个虚无缥缈的未来,精疲力竭。


然而实际上,“不确定”并不可怕、失败也不可怕,重要的是面对这团迷雾的态度——享受挑战,享受探索,享受当下的一切。打磨心性,放慢脚步,“不是目标成就了你,而是你走向目标的每一步成就了你”。正确的方向是在达到目的的过程中,而非目的的达成;不是走入旅馆,而是走向旅馆;不是得到桂冠,而是追求桂冠。

如同《箭术与禅心》中所提到的“当下的真心”,重要的是在当前时刻保持完全的觉知和正念,而不被过去的记忆或未来的期望所干扰。专注于当前的体验和感受,放下自我的投射,去体验一种纯粹的存在状态。

人生不是竞赛,是探索。生命的旅途并非一连串的终点,而是一系列的旅程。每一个目标的实现只是暂时的停靠,而真正的意义在于如何珍藏好每一个当下,走向这些目标。

能量

生活上今年并不如意,所遇种种皆怀痛楚,长这么大第一次没有回家过年。痛楚的部分就不展开说了,反过来分享一下今年搜集到的一些能量光点吧。

来信中的能量

今年的来信比往年要少一些,只有 31 封。一直以来,我都认为回复来信是一件消耗能量的事情,在《AI 心理疗愈应用的探索与实现》中我曾描述过这种回信的体验:

然而坦率地说,在来信数较少的日子里,我会感到轻松许多。每次回复来信时,我都需设身处地,细细品味来信者的文字,努力与之共情——“若我是他们,在如此境遇下,我该如何应对?”随后,我会整理思绪,将自身的能量转化为回信的内容。通常,一封来信需要一到两周的时间才能回复,因为我自己也需要一个宁静的环境,以便做出最恰当的答复。尝试共情已然如此艰难,更难以想象来信者在现实世界的真实体验中会面对多少痛苦。

但后来,我发现自己可能错了。信件的回复并非是消耗,我在回信的过程中也会反思自己的处理方式,比如前文提到的那段回信,剖析了我自己对未来的执念。

此外,有些来信也同时在给予我意想不到的巨大能量。

年初的时候收到过一封读者的“还愿”:

祝您除夕快乐!看到您能朝着自己满意的方向前进,我感到非常高兴。我想借此机会向您表示感谢,感谢您对我的点拨(还有您的博文),告诉了我世上存在这么多的自我实现方式。

无论如何,感谢您能在最开始的回复中点拨了一位迷茫的学子,我也对即将到来的毕业和工作(希望能圆梦省考)充满了期待。希望您能事业有成,一切顺利!

我也很感谢你,当时读来没有更多的感触,但未曾想到时隔一年的信件会在未来的某一刻反哺自己能量。

以及前些日子另一位读者的一段文字:

在工作这块,我在 9 月读完《再见了,盛夏》后,转发给了我的朋友。问他读完什么感受。他和我说,他最大的感受是难过,字里行间的自由和对未来不确定的向往,是他可能这辈子都无法经历的。看到了有人这样生活,于是,开始审视困住自己的这一方天地。

其实这篇文章在博客下有许多留言,但来信的这段话让我的感受更加真切。那段时间我正处于巨大的迷茫之中,忽然收到这封来信,看着这段文字仿佛是看着从我某段人生里撕下的几页日记,又从中拾取了勇气和力量。

以及朋友来信的安慰:

或许当一个人落魄的时候,更应该爱自己。生活是很现实的,很少有人会越过一个人的外在条件去接触心灵。

我之所以喜欢信件的沟通方式,不仅仅异步沟通便于自己安排时间,更重要的是在《月刊(第 18 期):逃离社交网络》 中写到的那样:“写邮件代替细碎聊天也是一个非常不错的方法,可以沉淀自己日常的想法并锻炼表达力,让自己的关注点回归文字本身,赋予语言与情感最真实、细腻的纹理。并且写信本身也是一件较为庄重的事,可以培养生活里的仪式感。”

我不可能从当下的这一点上看到它与将来的关系,但过些年头之后再回头看,两者之间在一些维度上却存在着千丝万缕的联系。正如乔布斯在斯坦福大学毕业典礼中的演讲所说的:“我们不可能从现在这个点上看到将来;只有回头看时,才会发现它们之间的关系。所以你必须相信,那些点点滴滴,会在你未来的生命里,以某种方式串联起来。你必须相信一些东西——你的勇气、宿命、生活、因缘,随便什么——因为相信这些点滴能够一路连接会给你带来循从本觉的自信,它使你远离平凡,变得与众不同。”

他人的善意

除了读者来信中的能量,我也十分感谢这一年遇到的种种善意。

感谢所有的鼓励。 在离职期间得到了老朋友的礼物和鼓励,同时意外收获了一些新朋友,让我在前进的道路上坚定不移。

感谢所有的欣赏。 自从自己出来单干之后,不会再有上级的肯定和同事的反馈了,我对自己所做事情的价值都会画上问号,会考虑很多、质疑很多、否定很多。但是用户的反馈让我明确了它是有意义的、用户的付费让我知道它是可以健康走下去的。

MoFlow 上线前夕,网易云音乐联系到我希望聊一聊合作,视频会议开启的时候我才发现对面坐着的人是两个 VP 和一个部门的负责人,是 1v3 的会议。我们聊了产品的愿景、核心指标的制定、产品可能会面临的问题,一切都很愉快。在会议即将结束之际,我问他们:“可是这些和你们都没有关系呀,在聊之前这也是我所困惑的,没有领域交叉的话,合作对于你们有任何收益吗?”他们说:“合作其实只是一个建立联系的契机,我们看到你的履历和正在做的事情,很欣赏你。认识你,这才是最重要的目的。”我只能打趣道:感谢友商老板的欣赏和肯定。但这一切,确实给予了我很大能量。

感谢所有的际遇。 不仅仅是生活中遇到的人们,还有一些陌生人的留言。这本是一句可有可无的留言,但它的存在让我真切地感受到我们之间的交集:

感谢所有的故事。 在今年《再见了,盛夏》发布之后,许多人留言了自己的故事,让我见识到了人生中各种奇妙的可能性。其中有一条微信,让我感慨良多:

她说“做可以想象的事情才能让自己觉得活着”。钦佩之余,我将此谨记于心。

一口气行文至此,抬头看了下书店桌上的灯光,或是巧合,但更似是命运的低语:

人生天地之间,若白驹之过隙,忽然而已。——《庄子·知北游》

或许分离和死亡都不是最可怕的,可怕的是还没想好怎么过好这一生就已经走到了尽头,不论是挫折还是辉煌,不论是平淡还是精彩,都是生命中最好的礼物。

感恩一切。

书影音

因为是年终总结,这里照例列举下这年的书影音。

剧集

  • 《安娜》:★★★★★
  • 《追风者》:★★★★★
  • 《玫瑰的故事》:★★★★☆
  • 《庆余年 2》:★★★★☆
  • 《千万别回家》:★★★☆☆
  • 《执行法官》:★★★☆☆
  • 《漂白》:★★☆☆☆
  • 《猎冰》:★★☆☆☆
  • 《白夜破晓》:★★☆☆☆
  • 《九部的检察官》:★★☆☆☆
  • 《回响》:★★☆☆☆

电影

  • 《周处除三害》:★★★★★
  • 《我的阿勒泰》:★★★★★
  • 《误杀瞒天记 2》:★★★★☆
  • 《祭屋出租》:★★★★☆
  • 《异形:夺命舰》:★★★★☆
  • 《死亡录像》:★★★★☆
  • 《三大队》:★★★★☆
  • 《默杀》:★★★★☆
  • 《因果报应》:★★★★☆
  • 《飞驰人生 2》:★★★★☆
  • 《一个母亲的复仇》:★★★☆☆
  • 《瞒天过海》:★★★☆☆
  • 《浴火之路》:★★☆☆☆
  • 《惊天大营救》:★★☆☆☆
  • 《哥斯拉大战金刚 2》:★★☆☆☆
  • 《海王 2》:★★☆☆☆
  • 《被我弄丢的你》:★★☆☆☆
  • 《末路狂花钱》:★☆☆☆☆
  • 《维和防暴队》:☆☆☆☆☆

动漫

  • 《葬送的芙莉莲》:★★★★★
  • 《物理魔法师马修》:★★★★☆

阅读

  • 《鱼不存在》:★★★★★
  • 《有限与无限的游戏》:★★★★★
  • 《亲爱的我饱含杀意》:★★★★★(漫画)
  • 《六个说谎的大学生》:★★★★☆
  • 《哲学 100 问:后现代的刺》:★★★★☆
  • 《书写自愈力》:★★★★☆
  • 《不要相信你所想的一切》:★★★★☆
  • 《慢慢变富》:★★★★☆
  • 《箭术与禅心》:★★★☆☆
  • 《心灵书写》:★★★☆☆
  • 《夏日、烟火和我的尸体》:★★★☆☆
  • 《巴菲特教你读财报》:★★★☆☆
  • 《无:生命的最佳状态》:★★★☆☆
  • 《七个证人》:★★☆☆☆

游戏

今年依旧没有怎么玩游戏,看了下只有一款《塞尔达传说:智慧的再现》,没有通关。

写作

新年目标

29 岁,是一个可攻可守的年纪。这是一个充满可能性的年纪,希望在今年的破碎之后,我可以在而立之年重建秩序。

前几天在看《玫瑰的故事》,剧中最后用了一首诗作为结尾,我想把它作为结束语:

我轻松愉快地走向大路
我健康自由

世界,在我面前
长长的褐色的大路,在我面前
指向我想去的任何地方

从此
我不再希求幸福
我自己便是幸福

凡是我遇见的,我都喜欢
一切都被接受

从此
我不受限制
我使我自己自由
我走到我愿去的任何地方

我完全、绝对地主持着我


世界在我面前,指向我想去的任何地方,此后我不再希求幸福,我自己便是幸福。

愿世间万物,都能迎来开花结果之时。

月刊(第28期):AI 没有体验世界的能力

本篇是对二〇二四年十一月至十二月的记录与思考。

AI 没有体验世界的能力

MoFlow 于昨夜——2024 的跨年夜上线了 App Store,这标识着这三个月的忙碌换得了一个阶段性的结果。于是,我终于有时间来写写月刊了。

在做 MoFlow 这段期间,我被人问到最多的问题是 ——“AI 真的有可能帮助人们实现心理疗愈吗?”在这篇文章中,我想对这个问题做出一个简单的解答。

注:本文中的 AI 均指 LLMs。

Part 1: AI 与人类的区别 —— AI 没有体验世界的能力

谈及 MoFlow,我们不禁会探讨 AI 咨询师与人类咨询师之间的区别。我们说 AI 无法与人“面对面”的交流、无法与受访者之间实现“现实互动” ——比如语气中的细微变化、沉默中的含义、甚至是未曾言说的情感,这些都是基于情感复杂性而诞生的人类特有体验。

人类的认知不仅仅是信息的处理,而是一种通过感官与情感交织而成的体验。我们通过身体感知现实世界、通过情感去赋予事物意义。这种体验赋予了我们一种独特的理解方式,使我们能够在复杂的情境中做出判断和决策。

如果说体验是我们人类与世界互动的直接方式,它是感官的、情感的,是一种即时的存在状态。那么理解则是对这些体验进行反思、分析和整合的过程,是一种理性的活动。

AI 尽管在处理信息和生成文本方面表现出色,但它们缺乏这种体验的维度。AI 可以处理大量的数据,生成看似合理的文本,但它无法体验这些数据所代表的现实世界,它缺乏人类所拥有的直接体验世界的能力。

体验不仅仅是感知信息,它还包括情感、意识和主观性,这些是人类境遇的核心。体验是把人的内在意识与外在事实、个体与社会结合起来的关键(狄尔泰)。

而理解常常被认为是与体验密不可分的。我们通过生活的经历、情感的波动、以及与他人的互动来获得对世界的深刻理解。AI 虽然可以处理大量数据并从中提取模式,但它无法真正“体验”这些数据所代表的现实,无法满足“体验”的时间性和实践性。与此相对的,AI 的“理解”则是一种基于算法的模拟,而非真正意义上的领悟。

Part2: 概念的无限与语言的界限

接着,我们再来谈谈 LLM 中语言表达的问题。

在 2020 年的一篇《从《光·遇》出发,谈谈「游戏美学」》中,我曾写到:

由于人类有声语文符号的局限性,又由于事物属性的无限丰富,不可能有绝对严密的表达,何况情感与事物都是在不断变化发展的,一切的言语表达对事物的历史进程都只能是疲惫的追踪。

巴别塔事件时,上帝通过分化语言从而使人们之间无法沟通,让人类分崩离析。语言的意义并不由上帝规定,而由使用者规定,并且沟通的手段也不仅仅只有语言,还有无数种其他的行为可以拉近人与概念之间的距离。但仅仅只是拉近而已,概念本身是无限的,在《悉达多》中,也强调了语言无法完全传达真理和智慧。

逻辑学中,逻辑是一个对象在一个概念之下。我们可以将任意对象分为性质和个体,例如:

Fa: 孔子是哲学家

那么这个命题中的 a 即是个体常元,表示孔子;而 F 即谓词,描述某个个体是一个哲学家。我们可以发现,对于具体的个体我们无法把握,在所有的艺术表现中,我们把握到的仅仅是某个个体的特征。而我们心中一个概念的形成,就是由这些个无数视域融合后的结果。而我们的立足点越高,自身的历史视野、文化视野就越是开阔,越能够按照大和小、远和近去正确评价视野所及的范围内一切事物的意义。

此外,将主观经验转化为客观语言的过程本身就存在问题。观察者的主观感受是无法被他人完全分享的,因为他人不能成为观察者,也不能拥有与最初观察者完全相同的经验。正如维特根斯坦所说:“语言的界限意味着我的世界的界限”

因此,AI 基于文本的处理和表达方式也必然会导致理解与共情的困难。

Part3: AI 陪伴的寒潭倒影

前文我们谈及情感的真实性源于体验——人类情感的复杂性不仅仅在于其表现形式,更在于其根植于个人经历、文化背景和生物本能。AI 缺乏亲身体验的能力,这种缺失意味着 AI 的所反馈的情感表现极可能是表面的、缺乏深度的。AI 对人类情感的理解可能更像是一种镜像反射,而非源自内心的共鸣。正如一幅画可以模仿自然,但永远无法成为自然本身。

理解或许还可以是逻辑上的、计算上的,而共鸣则需要一种内在的体验和情感上的连接。

但这一切不是理解、不是共鸣,而是如同寒潭中的倒影,冰冷而完美地映射着世界的表象。

在 2019 年我曾写过一篇《人工意识何以可能?》,这篇文章中探讨了 AI 是否可能具备自我意识。文中举例了“中文屋实验”和“哲学僵尸”来论证了人类具有独特的主观体验能力,这种体验是私密的、无法被直接观察的。但我们可以确定的是,目前的 LLMs 缺乏真实的内在体验。

情感是人类体验的核心,它不仅仅是对外部刺激的反应,更是我们内在世界的真实表达。然而,当模拟情感的技术变得无处不在时,我们可能会陷入一种情感的“稀释”,将复杂而深刻的情感体验简化为可复制的模式——这也就是目前市面上各类 AI 陪伴软件的普遍陷阱。

情感的独特性来自于个体的生命经历和文化背景,每个人的情感都是其生活故事的反映,是其与世界互动的结果。我们需要珍视个体经验的独特性,而不是将其归结为普遍的模式。我们可以通过 Prompt 指定 Agent 的背景故事,我们也可以指定它的人际关系、性格、爱好、人生经历等等,但是 AI Agent 没有经验过这一切,于是将一切转成模拟,给用户的情感反馈也沦为表现化、模式化。用户可以在陪伴类软件里创造复数个 Agent,和它们聊天,好奇想看看这种人设具体会怎么应对我们的情感。即便存在模式化的问题,大不了聊腻了就创造下一个。

但我们真正需要让用户培养一种批判性的意识,去辨别哪些情感是我们真实的内心体验,哪些是由外部技术所引导的。我们需要保持人类情感的真实性,这需要我们不断地进行自我反思和自我理解。只有通过深入的自我探索,我们才能真正理解自己的情感来源,并在这个过程中发现情感的真实价值。

在这个情感模拟的时代,我们需要的不仅是单纯陪伴类的产品,更需要对人类情感本质的深刻理解和尊重。只有这样,我们才能在这个复杂的世界中,保持情感的独特性和真实性

Part 4: 在实践中诞生的意义

AI 的情感理解仅仅是模拟,那么人类与 AI 之间的互动是否会因此失去更深的意义?

在人类交往中,意义常常源于情感的真实交流、共鸣和理解。

然而,值得注意的是,意义并不仅仅来自于情感的真实性。意义也可以从功能性、实用性和结果中产生。例如,当 AI 帮助我们解决复杂问题或提升生活质量时,这种互动本身就具有意义,即使情感理解是模拟的。

此外,我们也可以反思人类自身的情感体验。我们的情感是否总是真实的?或者它们是否也受到社会、文化和生物因素的“编程”?如果我们承认人类情感有时也是一种复杂的“模拟”,那么 AI 的模拟情感是否就显得不那么不同?在《人工意识何以可能?》的最后,我曾提到过:

中文屋实验忽视了工程学维度,若真正实施起来必然需要构建一套模型或者函数,符号虽然不具备语义,但输入输出之所以是可预测的,那还是人的意识所赋予的结果。其实在决定模型或者函数之时,就已经构建立起来了形式语义,这就不仅仅是语法的了,而是语义的了。确实,就目前的 NLP 领域研究而言,无论是经验主义进路去构建深度学习模型,还是以理性主义进路去研究形式逻辑,在构成系统的时刻起,其实就已经是语义的了。后者自不必多言,而前者在喂数据时,监督学习自带的标签就是人为所赋予的语义内容。

因此,所谓的“意义”不在于体验、不在于理解,更多地取决于我们如何选择看待和使用这些互动。AI 的情感模拟可以被我们视为一种工具,它可以帮助我们更好地理解自己、促进人类之间的情感交流、甚至在某些情况下提供情感支持。因此,AI 与人类互动的意义,或许并不在于情感的真实性,而在于我们如何赋予这些互动以价值和目的。在探索 AI 的过程中,我们不仅在观察和创造,也在被观察和被重新定义。这种双向的互动或许正是通向真理的必经之路。

Part5: MoFlow 追求的答案

真正的理解和智慧不仅仅是信息的积累,而是通过体验、反思和情感所获得的深刻洞察。这是人类独有的特质,是我们在面对 AI 时应当珍视和保护的核心价值,也是 MoFlow 的产品理念。

在 MoFlow 所有的设计中,我们都围绕着“使用者”为核心,功能中淡化 AI 的存在,仅充分发挥 AI 的工具属性,规避情感陪伴属性。

例如,在 MoFlow 写完自己的经历之后,MoFlow 会默默提取你的想法,并将其中正效价的想法和情绪做显化处理,当你不经意看到这些外显能量时,你会潜移默化地去培养起更加积极的信念。而 MoFlow 鼓励用户进行正面的自我对话。

在《让心智快速成长的方法:提高自己的“主动性”》一文中,有这么一个论述:

“许多人会习惯性地采用消极的自我对话,比如:

  • 自我否定:我不擅长这个领域,所以我最好回避,别去碰它;
  • 自我质疑:这个问题好像很麻烦,是不是超出了我的能力范围?
  • 自我批评:我刚才的言行举止真是太糟了,我怎么会表现这么差?

这些对话看起来并不严重,但大脑是有一个特性的:它会相信不断重复的信息。这看似普通的自我对话,却会因为大脑的重复记忆特性,逐渐形成固定的认知模式。

久而久之,大脑就会相信它们,从而调低对自己的评价,让自己真的变成自己所反刍和念叨的样子。这就会极大地束缚我们的主动性,让我们在面临困难的时候,变得瞻前顾后、畏手畏脚,难以有效行动。因此,要产生改变,最首要的一步,就是把消极对话变成积极的自我对话。比如:

  • 我不擅长这个领域,所以我最好回避,别去碰它→ 我又有机会可以增长经验了。
  • 这个问题好像很麻烦,是不是超出了我的能力范围?→ 我是不是变得更厉害了呢?不如拿这个问题来试一试吧。
  • 我刚才的言行举止真是太糟了,我怎么会表现这么差?→ 我已经比以前有进步了,也许下一次可以做得更好。

这里要特别注意:很多书籍可能会教你「自我暗示」,比如不断告诉自己「我很棒」「我很强大」「我很厉害」—— 但是,这是错的。

为什么呢?研究发现:过于空泛、不够具体的自我暗示,以及大脑本身不相信的自我暗示,不仅是无效的,反而会造成反效果。它反而会把问题凸显出来,让原本没那么严重的问题显得更严重。”

MoFlow 会当你面临一个难题时,笃定地告诉你:“你解决过相似的问题,你有触类旁通的经验,有足够的能力足以去应对它。”“哪怕出错了也没关系,它也能丰富你的生命,成为你新的经验。”

现在的 AI 已经可以通过分析大量数据,揭示出人类在某些情况下的普遍行为模式,帮助我们更好地认识自己和社会。但最终,真正的理解仍需回归到个体的体验与反思中。

AI 的理解是一种工具性的理解,而非存在性的理解。它可以协助我们更好地理解人类境遇的某些方面,但无法替代我们通过亲身体验和内心反思所获得的深刻洞察。因此,MoFlow 中的 AI 设计更多是引导式、启发式的,以此来鼓励用户自我对话、自我成长、自我关爱。

关注、关心和关爱——这些都是我们会收到的礼物,同时也是我们可以用来赠予他人的礼物,因为这些礼物只有在一种慷慨相待的生活中才会展现其活力。

我们学习理论,并不是为了通过吊书袋式的显摆来彰显自己的存在,关键在于如何改变世界。每一次实践背后都闪耀着众多灵魂的力量,这些努力最终都落到实处,为他人带来福祉——这才是我们追求的实践意义。

“AI 真的有可能帮助人们实现心理疗愈吗?”

——“是的,完全可以。”

🎬 书影音

以下是本周期的书影音记录。

  • 读完:传记 |《鱼不存在》| ★★★★★
  • 读完:心理学 |《无:生命的最佳状态》| ★★★☆☆
  • 读完:心理学 |《不要相信你所想的一切》| ★★★☆☆
  • 读完:小说 |《六个说谎的大学生》| ★★★★☆
  • 读完:科普 |《星星离我们有多远》| ★★★★★
  • 看完:韩剧 | 《安娜》| ★★★★★
  • 看完:网剧 |《白夜破晓》| ★★☆☆☆
  • 在读:哲学 |《世界观》| ★★★★☆
  • 在读:心理学 |《知识的错觉》| ★★★☆☆
  • 在读:哲学 |《我们为什么而活》| ★★★★☆

AI 心理疗愈应用的探索与实现

开篇:咨询来信及自我疗愈的思考

在过去的两年中,我持续收到了一些来自博客的咨询来信。这些来信促使我在今年上半年开设了一个专门的咨询服务,以作为一种异步沟通的渠道,帮助解答读者们的各种问题。迄今为止,我已收到近百封来信。有些来信者在我回复后明确表达了感谢,而另一些则杳无音信。我常常会思索,我的回信是否在他们的生活中产生了积极的影响——或许,他们已重返正常的生活轨道,因此不再需要回信。

然而坦率地说,在来信数较少的日子里,我会感到轻松许多。每次回复来信时,我都需设身处地,细细品味来信者的文字,努力与之共情——“若我是他们,在如此境遇下,我该如何应对?”随后,我会整理思绪,将自身的能量转化为回信的内容。通常,一封来信需要一到两周的时间才能回复,因为我自己也需要一个宁静的环境,以便做出最恰当的答复。尝试共情已然如此艰难,更难以想象来信者在现实世界的真实体验中会面对多少痛苦。

在这些来信中,我发现大多数人面临着相似的困扰和挑战——自我发展、人际关系、家庭关系、职业规划等各个方面,它们有着极大的共性。仅凭个人力量,我们很难从这些问题中解脱出来。观察来信者的倾诉,我意识到大多数人缺乏有效的方法、甚至缺乏意识去应对生活中的这些问题。每个人都是以高度个人化的方式去体验人类处境的压力,因此当问题来临时,人们往往选择自我消化,直到无法承受时才尝试倾诉。很少有人会系统性地去寻找、以及去解决这些问题背后的根本原因。

因此,我希望能够构建一个通用性强且系统化的疗愈方案,以规模化地处理这些问题,让个体可以通过这个方案实现自我疗愈。我将这个系统化的过程拆解为——表达、宣泄、理解、允许和探索。

五个环节

表达:作为载体的表达性写作

自我反思是解决问题的起点。在回复读者来信时,我也常常反思自己是如何探寻问题根源的。如果是我的话,我会选择一个安静的环境,将所遇到的困难和当前的处境在心中复盘,然后记录下来。在文字中,我努力寻找事件背后的隐藏因素。多年来,我养成了在睡前花费十分钟记录当天经历的习惯。

《月刊 (第19期): 日记的意义》中,我曾探讨过多年来自己写日记所发现的意义,一共有三点:

  1. 自我觉察的工具
  2. 折射生活的意义
  3. 记录身边的美好
日记的三个意义

日记不仅是记录生活的工具,更是一种自我对话和成长的方式。通过写日记,我能够将内心的混乱和困扰转化为清晰的文字,这个过程本身就是一种治愈。在安静的写作时刻,我得以暂时脱离日常的喧嚣,专注于倾听内心的声音。

写日记帮助我建立与自我对话的习惯,使我能够以更客观的视角审视自己的情绪和想法。通过持续的书写,不仅能够记录生活的点滴,还能观察到自己的成长轨迹,发现情绪的变化模式,从而更好地理解和管理自己的心理状态。

然而,写日记只是形成记录习惯的一个载体,它还可以是周记、随笔、备忘录等其它形式。这种写作方式在心理学上称为表达性写作,这是一种常用的方法,帮助人们处理压力和改善心理健康。在写作过程中,人们可以真实地抒发感受和想法,完全袒露内心。它有助于深入内心的孤独感,让我们重新连接自我,找到内心的宁静。

在牛津大学出版社的《积极心理学手册》第三版中写道:“在写作练习后的几个月里,那些写下了关于创伤事件的‘最深刻的想法和感受’的人,因病去诊所就诊的次数减少了50%。这种写作被称为‘情感披露写作’。为检验生理因素,研究人员设计了另一项研究。在写作开始前一天、最后一次写作结束时和结束六周后,对被试抽血检验,以便评估其免疫功能,结果发现,那些写下自己想法和感受的人免疫功能得到了很大的提高。这种影响在写作的最后一天达到顶峰,而且往往会持续六周以上。除了这一结果,研究人员还发现,被试经常称写作多么有用,能帮助他们理解和处理事情。”

表达性写作作为一种心理疗法,它揭示了人类内心深处的重要真相:表达本身即是一种解放。从哲学角度看,表达性写作是一种自我反思的实践,不仅是情感的宣泄,更是对自我存在的重新审视。通过书写,我们将内心的混乱外化为有形的文字,这一过程使我们能够以旁观者的视角重新审视自己的情感和思维。这种方法之所以有效,是因为它让人能够与自己进行对话,从而打破内心的孤独。如果能够将最深刻的想法和感受表达出来,这本身就是对自己的疗愈。

我们不能遇见自己,是因为我们没有把时间用在自己身上。表达性写作让我们学习温柔地面对自己,温柔地放下面具,用一种温和、温柔的方式贴近自己。

正如海德格尔所言,人类的存在本质是“在世存在”,意味着我们始终与世界和他人相连。但在当今快节奏、高压力的社会中,人们常感到与自己和他人疏离。表达性写作提供了一条重新连接自我、寻找内心平静的途径。

因此,我们将这个系统性解决方案的载体定义为表达性写作。其中,方案的重点不在于“写作”,而在于“表达”。接下来的问题是,如何使用这个载体去解决实际问题?

宣泄:在自由书写中觉察情绪

在表达性书写的静谈中,我们开始探寻情绪的深邃之源。根据理性情绪行为疗法(REBT)的基本前提——不是事件本身导致我们的情绪反应,而是我们对事件的认知和解释决定了我们的情感和行为。因此,首先我们要明确的是:已经发生的事情是无法被改变的,但记忆和体验是可以被改变的。这是疗愈的前提。

基于这个前提,可以推演出疗愈的首要目标是调节自身的认知与情绪。而在调节认知与情绪的过程中,最关键的一步是学会觉察和识别情绪,这也是我们这个系统性方案的核心。而识别的前提是能够觉察出情绪,那么我们该如何有效地觉察自身的情绪呢?

情绪觉察和我们的感受力息息相关,鲍勃·迪伦有一句话:“有些人能感受到雨,而其他人则只是被淋湿。”作为人类个体而言,每个个体对同样事件的体验是存在差异的,这取决于个体的感知和内心状态。

我们在生活中对事件往往仅是被动的接受者,并不会主动地感知和理解周围的世界。我们首先需过激活我们的感受能力,以便深入地探索自己的核心情绪,从而获得更丰富的生活体验。这种深刻的感知能力不仅能带来个人的成长,也能增强我们与他人的联系和理解。生活的艺术在于从每一个瞬间中提炼出有意义的体验,而不仅仅是经历。

重新激活感受能力的最直接方法是宣泄,这是一种情绪的自我修复机制。然而,在现代社会的背景下,我们需要一个安全的途径来进行无害的宣泄——我们给出的答卷是自由书写。我们鼓励个体在自由书写中释放自我,让思想得以放空,随心所欲地书写,无需遵循任何逻辑或修辞规则,不必在意排版、逻辑和语句的通顺。顺应思绪,让情绪自由流动。记录下今天发生的事情,书写自己的心情,这也是叙事疗法的一种手段。我们提倡书写,因为书写能够更好地实现情绪的分离与释放,也更有利于客体分离。

我深知,恢复感受力并非易事。将粗糙的感受精微化、细腻化,犹如在一摊浑水中筛选出闪烁的金子。然而,通过自由书写的方式,我坚信个体能够逐步开始觉察自身的情绪。因为在书写的过程中,我们已经学会了与自己“相遇”。

如果说自由书写是对内心的探索,那么接下来的步骤,则是对自我理解的深化。

理解:识别当下的核心情绪

在自由书写的过程中,我们常常通过焦虑等抑制性情绪来表达我们的反应。这一环节的任务是穿透这些抑制性情绪,探寻事件中的核心情绪,并将其从自由书写的文本中识别出来。通过这一过程,我们能够实现精准的情感疗愈,深入理解自身的内心世界。

因此在书写的过程中,我们最重要的任务就是找到自己面对这个事件时的核心情绪。这个过程如同在自己觉察千丝万缕的情绪间,找到引发它们的源头情感,也就是核心情绪。

核心情绪与其他情绪的关系图

核心情绪隐藏在各种抑制性情绪之后,我们在寻找的过程中很大几率会遇到困难,这需要引入一个新的概念——情绪粒度。就像画家能够分辨出常人难以察觉的细微色差,我们对情绪的感知也是如此。

如果对情绪的感知过于模糊笼统,比如只能说"我感觉很糟糕"或"我的情绪很差",那么每当感到"不好"时,都容易会陷入负面的身心反应。这种状态会不断消耗我们的精力,因为这样我们既不清楚具体要应对什么,也不知道该如何解决。当我们培养出快速辨别各种情绪的能力时,这些被清晰标记的情绪就会形成一道“语言结界”,守护我们的精神世界。

提高情绪粒度的方法有 2 种:学习新的词汇,以及为情绪贴标签。我们可以将这种情绪识别能力比作一道内在的“语言结界”,当我们学会给情绪贴上精准的标签时,这些情绪就不再是朦胧难辨的感受,而是转化为清晰可理解和处理的信息。这种能力既能保护我们自己,也能推动个人成长,因为它帮助我们更准确地认识内心的需求和边界。

然而,这种识别过程要求我们对自身感受进行理性化处理,这带来了新的挑战——在追求情绪识别和自我意识提升的过程中,我们如何确保不失去对生活中不可预知性和自发性的欣赏?此外,这种情绪识别的方式是否可能导致我们过于理性化自身感受,从而忽视了情绪的自然流动与变化?

这便需要介绍 MoFlow 的要点——让情绪自由流动。

允许:让情绪自由流动

为情绪贴标签确实可能在某些情况下导致人们过于理性化自己的感受。通过使用情绪识别工具,人们可能倾向于分析和标签化情绪,而非自然地体验和处理它们。这种过度分析可能使人忽视情绪的自然流动与变化,甚至可能导致对情绪的过度控制或压抑。

我们需要允许情绪的自然发生。比如,人类的负面情绪是一种“生存工具”,是为了更快地适应变化而进化出来的:愤怒提醒我们在遭遇不公时需要捍卫自己的界限,并赋予我们行动的勇气;焦虑提升我们处理问题所需的专注力;孤独感提醒我们对亲密人际关系的需求;悲伤则提醒我们刚刚失去了重要的人或物。没有负面情绪,我们便无法巧妙地应对外界的威胁。

除此之外,情绪也是推动行动的重要动力。恐惧是害怕某种事物,而忧虑则是害怕没有事物;愤怒促使我们去纠正不公,悲伤让我们更加珍惜和关心他人,而热情与激情则推动我们追求目标,实现梦想。人们因为失去爱而哀伤,又保持哀伤以重新获得爱。

人生经历的事件都是为了成就我们,换一个态度看待我们的过去,我们就可以从中得到资源和力量,找出规律,找到自己的人生使命,然后好好规划未来的人生之路应该怎么走。 苦难本身并不是目的,而是一个过程,通过改变我们对苦难的看法,我们能够从中汲取经验和力量。

因此,在追求内心宁静的过程中,重要的是找到一种平衡,既能保持内心的平和,又不忽视情绪的积极作用。这也正是 MoFlow 的 Slogan:“让情绪自由流动,书写疗愈力量。

Slogan

书写疗愈整体而言包括三个阶段,即觉察、识别和疗愈。在疗愈这一阶段,MoFlow 强调经历的即时性与着陆的治疗方式,引导我们接纳自我、允许情绪的发生,从而转向内心宁静。接纳,是我们愿意面对和接受事物此刻的本来面目;接纳,不是抑制情绪的发生,而是看着各种情绪和想法产生、发展、消失。

即时性和着陆

首先是“经历的即时性”,它指的是一种不关注脱离自身经验的事实的精神状态,简单来说,就是接纳事物的本来面目的态度,是个体在当下时刻对事件或情境的直接感知和体验。这种即时性强调的是对当前经历的全然投入和感受,而非对过去的回忆或对未来的期待。在《谈谈存在的价值与人生体验》一文中,我曾强调过“当下”的重要性,我们“唯一确信的存在是当下,唯一重要的是现在、是此时此刻的感受。人首先必须存在,才谈得上有关人生的一切”。换言之,如阿德勒所言:“人生是一连串的刹那,最重要的是此时此刻。”

其次是“着陆”,它是应用于心理治疗领域的一种方法,是将心灵拉回“现在”的各种技巧的总称。个体的真实存在在于对“此刻”的体验,而不是对过去的沉溺或对未来的焦虑。一个人能真正生活的只有当下,事实上,人不可能比当下活得更久——当下一直伴随着你。即便是在回顾既往生活的那一刻,即便是在生命的最后一刻,人仍然在体验着、在生活着。永恒的时态是当下,而非未来。生活在过去的阴影或未来的焦虑中,往往让我们忽视了当下的真实体验和存在的意义。这种观念帮助我们减少不必要的痛苦,避免伤口再被“第二支箭”射中,让自我“着陆”,找到内心的平和。

这也是接受与实现疗法(ACT)的核心理念:强调接受现实和承诺行动,通过帮助个体接受无法改变的事情,专注于当下,并根据个人价值观采取行动,从而实现心理灵活性。

《书写自愈力》一书中说:“疗愈,是指我们看到自己的伤痛,理解伤痛的由来,进而转化创伤情绪之后,我们在生活中就不会再像以前那样对特定的事件产生特别强烈的情绪,从而能够客观理智地思考和行动;疗愈,是指张开心灵的眼睛,看到内心被伤痛掩盖的、难以表达的需要,不在内心驱使自己,从而可以减少甚至消除内耗,更加从容自在地面对生活。”

但我们必须承认的是,并不是所有的书写都会通往疗愈,如电影《好东西》中铁梅写了自己单亲妈妈的经历却遭网暴。生活中有许多不确定性和痛苦是不可避免的,但我们可以选择如何回应这些挑战。接受现实并不意味着放弃,而是通过接受来找到内心的平和,并在此基础上采取积极的行动。

这是一场向内的旅程——专注此刻,当自我沉浸于记录当下的感受时,允许情绪的发生,让情绪自由流动。请允许自己去体验这些情绪,在经历整个过程之后,你会得到生命的礼物——“怀有迷惘的时候,心被景色所包围。悟道之后,景色被心所包围。”

探索:MoFlow 心理疗愈的场景

以上是 MoFlow 的设计理念,围绕着这套理论结构,我们探索并设计了一系列的产品功能。

情绪追踪

MoFlow 提供了基础的情绪追踪功能,允许我们在无需书写任何内容的情况下,通过简单的情绪打卡来记录当下的情绪状态。MoFlow 鼓励你将目光转向内在,每天留出时间倾听内心的声音,了解自己真实的想法:此刻的我快乐吗?我感到幸福吗?我真正追求的是什么?我在害怕什么?我在逃避什么?我需要做出哪些改变?我该如何活出真实的自我?只有当你愿意花时间静心聆听,才能听见内心最真实的声音。

在洞察模块中,MoFlow 将展示你的情绪日历,以及情绪与活动之间的关联关系。这一功能不仅帮助我们直观地了解情绪变化,还揭示了情绪与日常活动之间的潜在联系,为更深入的自我觉察提供了支持。

情绪追踪

书写助手

MoFlow 的书写助手与书写体验实现了无缝结合。当你在自由书写过程中遇到思维瓶颈时,可以唤起书写助手,以辅助我们进行思维扩展。通过这一引导,我们能够书写出更多内容,从而深入挖掘自身的核心情绪。这一过程不仅促进了自我表达,也为情感的深入探索提供了支持。

在思维层面,如果你在书写的当下对自己“为什么如此”感到好奇,那就可以采用自我追问式的方法,也叫作苏格拉底式提问。在心理咨询和心理治疗中,提问是基本的谈话治疗工具,并且提问也能体现心理咨询师的功底。MoFlow 提供了追问工具,你可以在书写中自己向自己提问。未来书写助手会得到更多的特性支持,以便更好地辅助我们进行自由书写。

书写助手

书写报告

在书写完成后,MoFlow 利用精调模型对你的书写进行个性化分析,生成结构化的书写报告。针对不同的核心情绪,模型提供相应的应对措施。这种分析不仅帮助你更好地理解自身情感,还为情绪管理提供了具体的指导路径。通过 MoFlow 的分析,我们不断擦拭心灵中起雾的那面镜子,擦掉虚假自己与真实自己之间的模糊,让自己越来越真实,这也是我们拉伸自身心理弹性和拓宽心理边界的过程。

书写报告

想法提炼

书写过程中,思想逐渐凝聚。所有的心理治疗都基于一个事实:在某种可控的情况下,大脑中的神经元会以可预测的方式移动并相互连接,然后形成新的联系。如果没有脑细胞的重构,学习将不可能发生。

在某些特定的书写内容中,MoFlow 会触发认知行为疗法(CBT)分析,试图挖掘我们想法背后的核心信念。通过依托想法分类模型,MoFlow 能够处理想法之间的依赖关系。这一过程不仅深化了对自我思维的理解,也为你提供了更为系统的思维整理和情感解析。

想法提炼

行动建议

在一些特定的书写内容中,MoFlow 会结合辩证行为疗法(DBT),为你提供针对性的行动建议。MoFlow 希望通过技能训练,调节我们的情绪、提升人际关系能力,并增强对生活的掌控感。MoFlow 强调内心平和与外在行动之间的关系。在追求生命的最佳状态时,我们不仅要关注内心的接受与理解,还需积极采取行动,实现我们的价值观和目标。正如铃木祐在其著作中可能探讨的那样,生命的最佳状态或许并非完美无缺,而是一种在接受现实的基础上,持续追求个人价值和意义的动态过程。

行动建议

能量卡片

MoFlow 的能量模块会根据具体事件,提供相应的正念卡片和思考卡片。通过正念练习和自我反思,我们不仅能够更好地理解和管理情绪,还能将其转化为创造力和行动力的源泉。这种能量转化的过程,与显化理论中所强调的"吸引力法则"不谋而合——当我们保持积极的心态和清晰的意图,就更容易吸引相应的能量和机遇。此外,这一模块能够帮助我们培养好奇心:保持对周围世界的好奇,尝试新事物,探索未知领域,这些都是显化理想生活的重要组成部分。

能量卡片

共鸣时刻

在某些书写内容中,MoFlow 会向你展示与其当前情绪和事件相关联的历史事件、名人名言以及书影音作品。这一做法旨在更好地实现自我分离,通过外部参照物的启发,获得新的视角和理解,从而深化对自我情感和经历的洞察。

共鸣时刻

定期反思

MoFlow 提供了一项类似心理咨询回访的功能——情绪周报。通过这一功能,你能够自我反思情绪和行为,识别出哪些方面可以更好地平衡自我意识与自发性。此外,对于某些极端情绪,我们可能无法在当下进行理性的情绪分析,因此会通过定期回顾为你提供更深入的洞察和建议。

定期反思

安全隐私

MoFlow 高度重视用户隐私,提供端对端的通信加密和数据存储加密功能。在注册账号时,会为你生成一个专属密钥,所有数据均通过该密钥进行加密处理,确保未经授权的第三方无法访问。

安全隐私

陪伴疗愈

MoFlow 鼓励你享受每一个小的进步和意外的发现,享受使用 MoFlow 的整个过程。希望你能够去感受情绪流动的过程,而不仅仅关注疗愈报告的结果。为此,MoFlow 计划在未来推出一系列情绪疏导和陪伴功能,敬请期待。

陪伴疗愈

过去是我们牵绊的来处,未来是我们前进的动力。 未来,我们计划不断完善与扩展 MoFlow 的功能,目前释放出的版本仅是 MoFlow 自闭环中的冰山一角。

我们希望,MoFlow 能够让每个人都能得到属于自己的礼物——内在的安宁与平静。

我们期望,MoFlow 能够创造出一个更加理解、包容且富有同情心的社会环境。

“让情绪自由流动,书写疗愈力量。”——MoFlow。


目前,MoFlow iOS 端已在 App Store 开放预约下载,欢迎前往 App Store 搜索「MoFlow」进行预约。

更多产品详情可见:

本文作者 Airing,MoFlow Founder,博客 ursb.me。 本文配图 LZH,MoFlow 设计师。

Mac 小众软件推荐与工作流分享(2024)

这篇文章分享了我在工作这几年遇到过的好用的 Mac 软件,其中有不少我还在使用,它们在我工作道路上帮到了我很多,部分软件组合起来形成的工作流甚至让我的工作本身也变得愉悦起来。我希望能够把这些分享给大家。

一、笔记篇

首先出场的是笔记赛道的诸多产品们,实话说,其中的产品我都折腾过,最后选择了 Heptabase,除了偶尔作图或者写草稿的时候在用 Excalidraw。选择 Heptabase 只是因为它的设计最适合我自己的场景,并非其他产品不够好。

本节将笔记产品粗略分为常规笔记、双链笔记、数据库笔记、画板笔记,分类也并不是很严谨,只是它的主要功能在某个范畴内就这么划分了,比如 Obsidian Canvas 和 Logseq 都可以做白板,但这并非是它们的特色功能,因此没有分到画板笔记中。

0x01 常规笔记

1.1 Drafts

Drafts,作为开篇的第一款软件,把尊重留给 Drafts。这是十几年的老牌产品,同时有移动端,搭配快捷键和桌面置顶功能,我常用作临时笔记或者桌面便签。非常良心,基础功能免费可用,也可以用商城里预制的 Action。付费则可以自己用 JS 写定制脚本做一些 Action,扩展性非常强,蛮酷的。缺点是仅支持纯文本输入,不支持图片插入。

1.2 Bear

Bear,中文名熊掌记,挺可爱的一款笔记软件,功能完善,将笔记导出成图片/PDF 的功能非常实用。缺点是不支持 CDN 图片解析,在写一些需要发布到网站的文章时则不太方面。

1.3 Typora

[Typora],程序员们钟爱的笔记软件,支持完整的 Markdown 和流程图语法,简洁易用,功能完备。缺点是存储库不支持标签管理,本质上只是一个 Markdown 编辑器。

1.4 iA Writer

iA Writer,一款 wysiwyg 的写作编辑器,主打文字创作,写作时容易进入心流。

1.5 Apple Notes

一款被低估的软件,配合 ProNotes 插件可以支持 Markdown 输入、模板定义、AI 输入与 Deeplink 拷贝,配合 Hookmark 可以做任意软件的双链,此外再配合 Raycast 的 Apple Notes 插件可以做全文检索和快速打开。

1.6 Obsidian

Obsidian,笔记软件的集大成者,社区非常活跃,软件本身除了功能齐全、稳定之外,配合插件可以做到许多意想不到的事情。优点是支持全平台,文件完全本地存储。

1.7 NotePlan

NotePlan,除了传统的笔记功能外,特点是支持非常完备的日程和任务管理。但因为 AI 合规问题,退出了中国区的 App Store。

1.8 Agenda

Agenda,好几年前 App Store 的年度产品,特点也是支持完备的日程和任务管理。

1.9 Mem

Mem,海外比较火的 AI 笔记软件,支持快速裁剪、快速录入、AI Q&A 功能。缺点是数据云存储,并且编辑器输入体验比较差。

1.10 Flomo

Flomo,身边很多产品朋友在用的软件,简单易用,理念是让记录回归到内容本身。开源社区还有像素级的复刻产品 memo,Obsidian 中也有相关的集成插件。

1.11 Raycast Notes

Raycast 内置的 Notes,想对标 Bear 和 Drafts,目前还在内测中,体验了下偏简易版的 Drafts。后续还需要利用 Raycast 的存量生态好好打磨。

1.12 Lazy

Lazy,前两周刚发布的笔记软件,仍在内测中。体验之后发现它和 Mem 的趋于同质化,都是主打快速记录,只是 Lazy 做的更加极端,能从任意软件创建笔记(有点类似于 Hookmark + Drafts)。和现代主流软件一样,内置了较为齐全的 AI 功能。

0x02 双链笔记

双链笔记是 2020 年开始出现的概念,由 Roam Research 提出,后来被许多产品借鉴、效仿、集成、改进。

2.1 Roam Research

Roam Research,虽然被追随者效仿并超越,但是不得不提一下。在 2020 年 Roam Research 刚出来的时候就付费深度使用了,摸索了一套双链笔记的使用方法,可以见我写的这篇文章Roam Research 最佳实践——知识管理与任务管理

2.2 Logseq

Logseq,可以非常简单粗暴的理解成 Roam Research 的免费本地客户端版,但后续出了白板功能、插件市场、PDF 标注等能力,总体来说是当下体验双链笔记最好的选择,没有之一。但这一年来被 Obsidian 蚕食过多,商业化是个问题,开源社区堆积了很多 Issue 没有处理。

2.3 Reflect

Reflect,也是一款双链笔记,但突出日程管理、任务安排以及 AI 编辑功能,海外用户画像偏商务人士。

2.4 Tana

Tana,是我个人非常偏爱的一款产品,在双链的概念之上更进一步,万物皆 Node,每一个 Node 都可以定义多个 Supertag,而每个 Supertag 又是一个数据表定义,因此被套上 Supertag 的 Node 转瞬变成了 Notion 的 Database。并且 Supertag 本身存在继承关系,而 Supertag 内的 Field 也有属性和复用的能力。AI 功能和语音输入非常好用,提供的 API 也比较完备,其中 AI Workflow 的可玩性非常高,再结合 Perplexity 的 AI 可以在 Node 内自行实现 AI 搜索。

缺点是这个软件着实太复杂了,如果不花多点时间研究下是搞不懂的,对新手很不友好。适合喜欢掌控、整理、秩序的同学。内测了两年,目前在公测中,还是邀请制加入,每周五晚上它会更新个小版本。

0x03 数据库笔记

3.1 Notion

Notion,在 2017 年的时候开始使用,给了我永久的 Plus 会员。国内还有很多类 Notion 的软件,这里就不展开说了。去年 Notion 出了 AI Q&A 的功能,因此可以基于自己的内容直接生成了 RAG,非常强大和易用。但是我在 beta 版使用之后觉得 AI 的能力还是太弱,不知最近是否有所改善。

3.2 Anytype

Anytype,很早提出基于 Object(Type) 的 All in One 软件,但是研发进度着实太慢太慢,过了两年理念先被 Capacities 实现出来了,而且即便今天,Anytype 的完成度依然不高。

3.3 Capacities

Capacities,基于 Object 的笔记软件(或者叫收集软件?),你可以像面向对象编程那样,定义很多很多对象的原型,之后每有一个 item 产生,那么都需要归类到某个对象之下。Item 之间也支持双链,因此可以搭建一个资源网络。AI 功能也比较好用,API 的存在也衍生了不少玩法。月更大版本,每个月看他们的 Changlog 还是蛮期待的。缺点是他们的服务基本上每个月都会挂一次,服务挂了或者网络不好就完全不可用,迫使他们今年开始做离线存储的改造。

0x04 画板笔记

4.1 Heptabase

Heptabase,近三年来一直在用的笔记软件,特点是卡片笔记+画板,数据本地存储+云同步,我也把它作为 PDF 阅读器,因为 PDF 阅读期间的标注也会自动转成卡片(类似于 MarginNote)以便我后续统一处理。同时它也支持我在用的 Readwise,在网页上标注的片段也会自动同步成卡片。一般我会收集很多卡片,在写文章或者做分享需要输出的时候,使用画板功能整理、发散、创新。最近新出的 AI Insight 功能对我而言用处不大,一般来说笔记在记录的时候就已经梳理好内容了,不需要再 AI 帮忙做大纲了,除非是无脑复制的长文才可能有这个场景。期待后续 Heptabase 在 AI 上的探索。

PS. 如果需要试用的话,可以使用我的邀请链接 https://join.heptabase.com?invite-acc-id=4cbb8101-41a9-4961-a447-a423f080f288,你转成付费用户之后我们各得 $5。

我在内测阶段开始使用这款软件,至今近 3 年 5000+ 卡片。喜欢这款软件不仅仅是因为它本身足够好用,而且这个台湾小哥的开发者(Alan)我非常佩服,在大学毕业就创业做这款产品,并且写了四篇文章介绍自己的愿景(My Vision: The ContextMy Vision: A New CityMy Vision: A Forgotten HistoryMy Vision: The Knowledge Lifecycle)。功能开发非常尊重用户,关键细节点会发投票,并咨询用户这么选的原因。中间有一年多的时间,我每天早上上班时打开 Heptabase,都会有版本更新,而且每个更新都是 Feature,可见作者对这款产品的热情。

4.2 AFFiNE

AFFiNE,雪碧(ewind)团队创业打造的产品,特点是文档直接转画板、以及多人协作,每个笔记也支持 Notion 那样内置 Database。创意非常好,但是目前产品的编辑体验个人觉得欠佳。

4.3 Excalidraw

Excalidraw,严格来说不是笔记软件,而是作图软件,非常适合程序员做各种架构图、流程图,我平时开发的时候也会单独开一个画板来当成草稿本。

分享一个小技巧,Obsidian 的 Excalidraw 插件比网页版好用很多,相当于有一个画本客户端。之前在做复杂项目的时候,一个多月都在同一个画本里打草稿,最后项目做完后,一个画布文件有上百 MB 的大小,但是一点儿也不卡。作者也一直在精心维护着它,偶尔还会有使用的小功能,比如两周前还出了个 PDF 标注能力,可以把画本改造成 MarginNote。

二、效率篇

0x01 时间管理

1.1 Apple Calendar、Google Calendar

系统级应用,服务稳定,功能齐全,也是免费的。iOS 18 的 Apple Calendar 也支持集成提醒事项,非常易用。Google Calendar 的优势则在于丰富的三方集成。

1.2 Fantastical

Fantastical,可以整合多种日历源和任务源,设计精美。

0x02 时间追踪

2.1 Rize

Rize 是一个时间追踪软件,曾经在我的《月刊(第23期):多任务中的时间管理》 介绍过它。

除了时间追踪,它更是一个培养专注的工具,同一软件或是保持在同一工作上下文中保持一段时间,会进入 Focus 状态(类似心流),这个时间会统计 Focus 时间。而如果你突然切出上下文,如打开微信或者用浏览器浏览了这个上下文无关的网页,Rize 会认为此刻你分心了,从而弹出保持 Focus 提示并退出此轮 Focus 计时。

整个软件设计地异常简单,整个流程不需要去配置任何东西,结合 AI 进行全自动的监测,只有在娱乐或者是打断 Focus 的时候才会感知到它的存在。(PS: 这不是广告,但如果想体验可以用 https://rize.io?code=291287 链接来注册…)

0x03 任务

任务软件有非常多,相信读者们也有自己用的顺手的任务管理软件。只要用的顺手,最基础的系统的提醒事项就足够用了。本节只列举 3 个具有代表性的产品。

3.1 Todoist

Todoist,老牌的任务工具,免费 5 个项目的限制足够使用。我之所以一直使用它,是因为它的 API 丰富,同时支持丰富的筛选语法,可以做很多集成,配合工作流使用。

比如能和 Obsidian 集成,这样 Daily Note 就能直接展示今天的待办事项,或者在项目笔记里可以做项目任务的集成,服务稳定、扩展性非常强,技术支持也很友善、积极。

3.2 滴答清单

滴答清单,适合中国宝宝体质的任务软件(工作事项繁多、存在多线程并行场景),功能丰富,但是始终保持着易用。

3.3 Linear

Linear,适合研发项目管理,集成能力多、可以同步跟进多个项目的工作量进展与进度预测。软件设计非常出众,设计圈内称之为 Linear 风格。

0x04 研发

研发软件每个技术领域都有不同,这里列举一下

  • Warp,开箱即用,颜值也不错,最重要的是完全不需要自己去折腾各种配置和插件,代替了之前自己使用了很久的 iTerms。也直接生成分享链接给他人展示自己的终端内容,发给他人定位问题的时候比较好用。
  • Cursor,最近大火的代码编辑器,AI 功能强大,比 GitHub Copilot 体验好很多。虽然 GitHub Copilot 对我的账号一直是免费使用,但我转 Cursor 付费了,后者甚至有和 AI 结对编程的感觉。
  • Apifox,API 文档、接口文档工具。
  • oh-my-zsh,用的比较多的是 git alias、git 插件、zsh-autosuggestions 插件。
  • tig,命令行 Git 增强工具,非常使用。
  • Homebrew,Mac 必备的软件管理工具,也不算小众了。
  • musicfox,命令行听歌工具,因此电脑上没有装音乐软件,通过 Vim 快捷键可以操作,还可以连接 Last.fm。
  • Scrcpy,安卓开发时使用,一般使用真机配合 Scrcpy 投屏,方便电脑操作或录制。
  • iPhone Mirrors,新系统支持的功能,算是姗姗来迟,着实非常好用,可以用来 iOS 开发直接操作真机,不需要再低头操作了。
  • Git Kraken,一般较复杂的项目和场景下才会用,比如大仓里包括很多 submodule,或者需要溯源很长的 commit 的历史去查某个地方的变动,否则直接用 tig。

0x05 AI

5.1 Perplexity

AI 搜索软件这一年来因为门槛降低,涌现了不少产品,之前整理过一波:

这里重点推一下 Perplexity,基本上它在我这里已经取代了 Google,它会结合搜索引擎的结果进行回答,同时也支持识别 PDF 文件和图像,回答质量对比同类产品来看是非常之高,最近出了 Mac 客户端版本非常方便。

PS: Perplexity 的免费版也足够使用(免费版只是不能用 GPT-4 而已),如过注册的话可以使用这个链接 → https://perplexity.ai/pro?referral_code=0ZSAD0VT ,未来如果想要付费的话可以得到 $5 的抵扣。

5.2 Raycast AI

AI Chat 客户端也非常多了,较上面的 AI 搜索而言更是不胜枚举。这里提一下 Raycast AI。

结合 Raycast 使用很方便,可以通过 Prompt 自定义 AI Command,以下是我常用的一些命令:比如提取 WeChat Bot 返回的 JSON 配置、JSON 压缩、输入 URL 总结网页内容、根据 JCE 协议转 ts 声明或者直接 Mock 出数据配合 Whilstle 构造请求的响应体。

5.3 Dify

Dify,我愿称之为成年人的大玩具!各种小模型基于组件节点自己搭起来,和小时候搭积木的感觉一样,再配合知识库、API 服务等能力,可玩性取决于你的创意。有开源版本,自己部署在服务端的话比较吃机器配置,可以本地部署或者直接使用 Cloud 版本。

0x06 工作流

  • IFTTT,自动化服务,支持 AI、Webhook,并且支持 JS 编程处理信息格式和过滤操作,可以做各种记录的聚合、整理和转发,可以作为常驻稳定的中转服务。
  • Zapier,类似 IFTTT 的自动化服务,价格较前者更贵,但更加易用,适合不想写过滤脚本的场景。
  • n8n,类似于 IFTTT 的自动化平台,开源免费,自己部署即可。

有了自动化之后,可以做很多有意思的事:

  • GitHub Star 总结:GitHub 点了 Star 之后,自动请求发给 Perplexity API 让 AI 去总结仓库内容,拿到结果之后再调用 API 同步到 Tana / Capacities 等笔记软件内收集。
  • RSS 监控舆情周报:订阅的 RSS 渠道如果触发到某个关键词,就请求 Perplexity API 拿到文章总结,之后同步到笔记软件内收集,调用通知接口提醒,并发到 TG 的个人频道里。最后利用 Email 做 Weekly 收集,每周发送汇总邮件,汇总触发到这个关键词的文章和对应的简介。特别适合用来做舆情监控。
  • 豆瓣书影音收集:类似于 GitHub Star,豆瓣评分了某个作品之后,收藏到笔记软件里。
  • 微博动态订阅:类似于 GitHub Star 订阅。
  • 听歌记录周报:实用 Musicfox 命令行软件听歌,之后配合 Last.fm 可以汇总听歌数据,每周发给你本周的听歌记录。
  • 工作专注日报:Rize 每日的专注邮件讲给 IFTTT 监听,如果有 Rize 的邮件,就会使用 IFTTT AI 自动总结今日的时间报告发送给自己。

0x07 阅读

  • Readwise Reader,和 Readwise 的集成做的非常好,体验流畅,支持快捷键、RSS 订阅、NewsLetter 订阅、也可以高亮图片。
  • Inoreader,RSS 订阅源管理,配合邮箱、IFTTT & AI、Telegram 订阅、关键词监控做一些自动化提醒和总结。
  • 微信读书,AI 大纲和 AI 解读十分好用,阅读完之后配合 Readwise 保存书摘。
  • Cubox,网页收藏箱,配合 IFTTT 使用,各订阅渠道最终都会收录到 Cubox 里。
  • Instapaper,因为免费+集成化做的很好,配合 IFTTT、浏览器插件、Inoreader 做网页收藏的中转服务。
  • Follow,基于 Web3 和 RSSRub 搭建的阅读器,前几周新出产品,目前还在内测中,需要邀请码可以找我要。
  • Reeder,老牌 RSS 阅读器,前几天出了新的大版本转订阅制了,也是开始转型主打多信息媒介订阅。
  • Me.bot,前段时间新出的一款网页收藏工具,基础功能类似于 My Mind,但是它基于角色卡做的 AI 助理还是蛮有意思的,性格鲜明。

0x08 系统

8.1 截图

CleanShot X

CleanShot X ,一款老牌 & 功能强大的 MacOS 平台的截图软件,以上两款软件的功能它都有,除此之外,还有录制视频、录制 GIF、上传图片到云端、顺序标注等独有的能力,界面也很美观,和 Raycast 的联动做的也很好,还有独创的「all in one」模式。

Snipaste

Snipaste ,Windows 和 MacOS 平台皆有,最喜欢的是可以把截图贴在桌面上的能力,如同便签一样,方便我贴重构稿、关键信息、对比代码、二维码等信息到桌面上。

Shottr

Shottr 是最近新出的又一款 MacOS 平台的截图 App,功能强大、界面美观、而且免费,较于 Snipaste 多了滚动截图、窗口截图、OCR 等能力。

8.2 翻译

  • Bob,翻译软件,有不错的快捷键支持,用的较少,大多数情况下会用 Raycast + AI Command 翻译。
  • 沉浸式翻译,比较好用的网页翻译,可以指定翻译接口。
  • Monica,是一个 AI 工具软件,类似于工具箱集合,内置的翻译则是像素级借鉴沉浸式翻译的功能,此外它还内置了网页总结、PDF 总结、视频总结、博客总结、搜索总结等等 AI 小工具。

8.3 其他

  • Arc,强大、易用的浏览器。(但是前几天官宣停止更新新功能了)

  • Stats,状态栏展示设备 CPU、内存、网络、磁盘、电源灯信息。

  • Bartender 5,也属于 Mac 必装软件了,可以控制状态栏展示。

  • RunCat,用小猫猫的状态来表示 CPU 的状态,挺可爱的。

  • Vimac,使用 Vim 快捷键控制 Mac。

  • NinjaMouse,配合 Vimac 使用,切软件的时候会自动把鼠标指针挪到激活窗口的正中间。

  • Input Source Pro,切换软件时自动切换输入法,做研发时很常用,如切到 IDE 或终端时自动切英文输入法,而切到文档或者企业微信时则自动切中文输入法。

  • 微信输入法,非常好用的输入法。

  • AltTab,软件切换工具。

  • Hookmark,配合 Drafts 使用,方便做任意书签的笔记。

  • Geekbench 6,本机性能测试软件。

  • AlDente,电源充放电管理软件,可以更精准的、个性化的控制电源策略。

  • Tencent Lemon,硬盘清理软件,小巧实用。

  • Paste,比较知名的剪切板软件,特点是支持移动端、优秀的检索能力,缺点是价格较贵、竞品也较多。

  • Raycast,入了 Raycast Pro + GPT 4 扩展包,最常用的功能是 HotKey 快捷打开软件 、AI Chat 与 Clipboard History。下面是一些常用的插件:

    • AI Chat & Quick AI,电脑上一键呼出 AI,这就显得微软的 Copilot 独立按键很蠢了。
    • AI Command,根据自定义的预制 Prompt 执行复制的文本,一般我会让它提取 CI 结果中关键的信息,然后生成我需要的配置;或者让它格式化压缩 JSON;分析堆栈信息;英汉互译等等。
    • Clipboard History,之前入过 Rewind,发现完全没有必要,直接 Clipboard History 效率更高
    • My IP,研发原因经常要看内网 IP,快捷键直接查看+复制。
    • Google Search,因为 Arc 默认搜索设置成了 Perplexity,当明确想用 Google 搜索的时候会按下这个快捷键。
    • QR Code Generator,工作原因有时需要 URL 转二维码,这个就很实用,不需要打开浏览器利用插件或三方工具转。
    • Todoist,配合 Todoist 使用,可以在状态栏或弹窗显示今日任务。
    • Format JSON,有了 AI Command 之后这个插件很少用了。
    • Linear,配合 Linear 使用。
    • Scrcpy,配合 Scrcpy 使用。

月刊(第27期):旅居

本篇是对二〇二四年九月至十月的生活记录与思考。

🌿 旅居与创业

9 月 27 日办理资产退库之后,我告别了深圳,告别了自求学起生活了十年了广东,正式开启了我的新生活。

离开前的一晚,和 Kami 在万象天地的小酒馆交谈甚欢,他感慨道,认识我这么多年,第一次见我如此健谈。或许在公司时,我给人的印象总是“内敛且不善言辞”,但那只是因为我选择将精力主要关注在事业之上。

在这之前的一周,我与同事们一一告别。令人意外的是,大家不约而同地最怀念的竟都是那段从零到一研发 Flutter 混合框架的岁月。那些共同奋斗、彼此成长的日子,早已深深镌刻在心中,感恩于这段珍贵的时光。

这段时间告别老朋友的同时,得益于上一篇全网加起来 10 万+阅读量的文章,也因此遇到了一些新朋友。有付费听我说故事的,有几封来邮件交友的,甚至还有一些曾一起共事的同频人,竟在离别之际才算真正认识上。

人生在世,理应结识形形色色的人,去聆听他们的人生故事。毕竟我们的人生仅此一次,但若能与他人的生命碰撞,迸发出璀璨的火花,那将是何等的幸运。

告别深圳之后,我驱车自驾来到了大理。租了一个小院子,每天埋头研发。说是旅居,其实更像是换了一个安静、凉爽的地方做自己的事情。但这一切,其实早已不是自己一个人的事情了。

原本是自己的事情,这个想法萌生于几年前,之后做了大量的市场调研、产品调研和用户调研,前几个月,设计合伙人正好处于 Gap 期,于是我们约着又讨论了想法,进行了补充和完善,最终将产品定义在 AI + 心理的赛道上。

目前为这个项目组建了一个六人团队,四全职两兼职,大伙对于项目充满激情,一起凝聚想法、一讨论、一起设计、一起实现,每天充实且开心着,让我想到了在前司做 Flutter 的那段日子。

回到项目本身,现在项目进展良好,预计 11 月底可以开启 TF 外团测试。根据目前的设计和内团体验来看,大家都迫不及待地想让这款产品上线,团队内的小伙伴都认为它着实可以帮到自己。

但我们也时刻保持着冷静,不断体验与打磨产品细节,如果 11 月底没有如期进入 Beta 版测试,那肯定是大家对自己的产品细节还不够满意。我们希望能够给用户们呈现一个好用的、完整的产品,希望它也能够给大家带来帮助。敬请期待。

🌺 生活点滴

以下是本周期内一些瞬间的记录。

🎁 礼物:

非常感谢朋友们送的礼物(或者米饭),完全的 surprise!

FotoJet.jpg

那段告别的日子相较于伤感,更多的其实是愉悦,感恩这一切。

4571730261295_.pic.jpg

🚗 国庆自驾:

没想到小 Mini 这么能装,我们带上了两只猫、两个 24 寸的显示器、全装了衣服的 20 寸大行李箱、一个鹅绒被、两个枕头、两套床单,还有几个背包。

装完满满一车之后,驱车三天行程 1800 公里,来到了大理。

4641730261540_.pic.jpg

🏡 旅居与小院子:

到了大理之后便在苍山下租了一个带二楼露台的小院子,早晨,阳光洒进院子,显得格外温馨;下午,在露台上饮茶办公,也是绝美。我最喜欢的是,这片区域非常安静,非常适合沉浸式办公。

🌊 洱海:

来到大理的这一个月来,每天 12 个小时工作时长,就只有一个周日出门了,那天环洱海自驾了一圈。

WechatIMG467.jpg
WechatIMG465.jpg

💰保险:

自驾途中,后窗玻璃意外破碎,于是我报了人生中的第一次保险。开始以为是换个玻璃就好,结果原厂的配件号没有后窗玻璃,4S 店说只能整个敞篷一起换。原本还是有些难过的,心里用“碎碎平安”来安慰自己,但出险一次,整个敞篷得以更换,倒也不算坏事。

🐱 丢了两条命的泡芙:

来了大理之后所遇种种并不算顺利。某天下午泡芙和手套从小院子的栏杆缺口越狱了,发现猫没了之后立马出门找,毕竟之前一直是家养,出门之后也不知道会遇到些什么。结果一出门就看到了手套,它着实太笨了,一出门就掉进了家门口的水渠里,跳不去来,只能喵喵叫(可惜没先拍张照片做个丑照纪念)。但是泡芙找了一圈也没有找到,最后不得已挂了寻猫启示,邻居看到了就送回来了,说泡芙一整天都在他家的阳台上躺着晒太阳,还以为是野猫。

泡芙回来之后的第二天,就开始不断吐口水,甚至开始在地上疯狂抽搐,抽到最狠的时候会强直身体,我也不敢碰它,担心它挺不过这一波。等缓下来之后,立马送去医院,初诊是中毒。但是吊了 5 天广谱解毒药配合护肝药也没有任何好转,甚至有加重倾向,每天抽搐的次数更加频繁,精神也愈加萎靡。

4591730261327_.pic.jpg

于是我们停止用解毒药,医院怀疑是神经问题,于是开始吃抗癫痫药,立马有好转,不再吐口水和抽搐了。但吃了一天之后,另一个问题表现出来——四肢软弱无力,导致它有两天无法正常行走。再送去医院,配合抽搐症状基本可以确定是神经问题,但医生说无法根治,国内也没有几家机构可以做检查,只能长期用药。于是开始吃维生素 B1 和 B12,未曾想吃了两天之后完全好转了。每天到处上蹿下跳跟个没事人一样,依旧调皮、依旧活泼。要说变化,是变得更加粘人了,同时也能听懂我们的一些指令,哪怕它玩的再欢,喊一声就会屁颠屁颠跑到你身旁。

都说猫有九条命,可能它又用掉了一条吧。上一次还是 4 年前,自己咬开阳台的封网走去阳台边缘,从 12 楼直接掉到了二楼商铺的楼顶上,第二天下午才找到。送去医院,检查之后发现只是受到了惊吓,同时咬破了自己的舌头,除此之外没有任何骨折和内脏受损,医生也说是个奇迹。

希望两只猫猫今后都能健康生活下去。

📊 最后的技术分享:

离开公司的最后一天做了一个技术课程,准备了足足两周有余,从加载到上屏整个流程如果要说清楚是一件比较困难的事情。我准备了一个 Demo 游戏,配合运行时断点数据与数学计算,讲解了渲染上屏的 10 个环节。把这个过程梳理清楚的过程非常有意思,也通过这个过程让我感受到了图形学趣味。

20241030121306@2x.png

因为时长问题,改过了 3 版的 PPT 大纲最终如下(反正放大也看不清楚就不打码了),原本会更系统一些,准备了 200 页+。

20241030121808@2x.png

✨ 创业分享

即时通讯:Slack

之前我们团队用的飞书,作为一款 All in One 的办公软件用起来确实很方便,至少会比企业微信高效很多。我们也在飞书里建立了若干个话题群,大家会在话题群里滚动发一些资讯的帖子,以保持对新鲜事物的敏感度。

使用飞书两个月后,我们发现了一些不太满意的地方:

  • 消息已读功能带来了阅读焦虑:飞书有消息已读,发完消息之后或多或少会有阅读焦虑,一方面会关注别人读了自己的消息没有,另一方面也会考虑着有新消息的时候需要尽快阅读。
  • 文档体验不流畅:飞书文档虽然强大,但用起来的整体体验并不能称得上是流畅,文档切换与检索会很容让人觉得知识库的设计过于繁重。虽然文档中的待办事项可以直接关联到飞书任务,但飞书任务也是一言难尽的难用。此外飞书免费版限制了 60 分钟会议,对于我们的周会讨论而言也是不够的。
  • 默认的三方集成有限:常用软件如 GitHub、Linear 等集成较少,需要我们自行开发 Bot 进行集成。

之后在 Slack 和 Discord 之间,我们选择了 Slack,有几点优势:

  • 公开的频道设计:频道的设计避免了聊天群的小圈子感,所有频道都是公开的,默认只邀请相关同学。对话题感兴趣的同学可以自行加入。我们还建立了许多消息频道,类似于之前飞书的话题群,每天滚动更新资讯,保持对产品的敏感度。
  • 高效沟通:Slack 会将连续的消息整理成独特的样式,期望用户像发送电子邮件一样,一条消息将事情说全。高效沟通需要在整理好背景、问题、原因和方案后,一次性发送消息,不必担心对方是否及时收到,可以当作留言。避免询问对方“在吗”,然后再慢慢发送消息,这既不是高效沟通,也不是即时沟通。
slack-message.png
  • 免打扰设计:Slack 的通知设计较弱,除非是私信或 @ 的消息,只有在活动/未读消息 Tab 才能看到。如果想在群里 @所有人,会弹出确认提示,因为这是一个很打扰的操作。同时,Raycast 有插件,可以方便地设置当前的 Slack 状态,配合定时消息和稍后阅读功能,确保不错过重要消息,同时也不打扰他人。
slack-notify.png
  • 轻量化语音会议:我们很喜欢 Slack 的抱团功能,很多国内办公软件称之为“会议”,需要预约,邀请时会有电话铃声响起,这种设计很严肃。但抱团不同,需要语音/投屏沟通时,直接发起抱团,对方按需加入。抱团类似于游戏中的“连麦”,是一种轻量、即时的设计,使每次沟通不再那么严肃。

  • 丰富的集成应用:众所周知,Slack 的集成非常丰富,我们集成了 GitHub、Notion、Linear、Loom、Xcode Cloud 等常用软件,结合自动化,几乎可以算作另一种意义上的 All in One。

slack-linear.png
slack-app.png

会议:Loom & Slack 抱团

前文提到了集体会议的时长会比较长,后来我们尝试做了一些优化。

首先,每周我会先用 Loom 录制一个异步会议,总结大家上周的工作和下周的工作安排,并整理下周工作安排的几个重点方案,团队成员有时间的时候浏览下视频和配套的文档即可。

之后,在方案进入设计研发阶段时,再在方案频道里使用「抱团」进行方案讨论。

这样就不需要约大家都有空的时间进行全体会议,约一个大家都有空的共同时间这件事本身就比较耽误项目的节奏。

loom.png

任务管理:Linear

团队使用 Linear 作为任务管理工具,它和 Slack 集成非常好,填好工作量之后,洞察功能可以对整个项目的研发进度给出预估完成的最早和最晚时间,这点对于 PM 把握项目节奏而言非常好。

linear-progress.png

此外,我们将项目每隔两周划成一个 Cycle,每个 Cycle 有 1-2 个主题,帮助我们专注于当下最核心的任务模块。

linear-cycle.png

最后,Linear 里程碑、任务约束等基础功能也打磨的非常好。

🎬 书影音

以下是本周期的书影音记录。

  • 读完:哲学 |《后现代的刺》| ★★★★★
  • 读完:小说 |《七个证人》| ★☆☆☆☆
  • 看完:电影 |《因果报应》| ★★★★☆
  • 看完:电影 |《异形:夺命舰》| ★★★★☆
  • 看完:电影 |《误杀瞒天记 2》| ★★★★☆
  • 看完:电影 |《默杀》| ★★★☆☆
  • 玩过:游戏 |《Thronefall》| ★★★★★
  • 在玩:游戏 |《塞尔达传说:智慧的再现》| ★★★★★

再见了,盛夏

一、近况、过往与未来

1.1 近况

譬如朝露。——《短歌行》

我提离职了,Last Day 定在了国庆节,裸辞。

当我突然向领导提出离职时,他表示很不解——大好前程、稳定高收入、大堆未解禁的股票、手上还有可以答 T12 的核心项目,不知道为什么我要离开。后来总监和 GM 找过我聊,我只是说我想体验更多的可能性,我在这里已经停留了太久了。按计划,我原本早该走了。

29 岁、小学老师、哲学硕士、毕业 4 年鹅厂 T11,很多标签环绕着我,偶尔拿到的奖项、优质的项目和熟练的业务,让我逐渐陷入到一个舒适圈里。我害怕一眼看到头的未来,我想跳出目前的境地,这条既定的大道,我不愿走完。

月刊断更的这个夏天,父亲确诊脑瘤住院了,术后又反复感染肺炎,愈后很差,经历转院 3 次、ICU 反复进了 4 次。每每去医院看望,都在数着我剩下可能能看望的次数。当我在病床前看向父亲时,才惊觉到父亲已如此衰老,在我的记忆里他一直如伟岸的山,过往回忆像走马灯在目光中闪烁。转瞬之间,29 年过去了,而我却不知我此生的时光还剩下多少。

所幸,父亲已得到治愈,目前处于康复阶段。我也决定离职,抛弃一切光环,走向不可知的未来。

1.2 过往

飘如陌上尘。——《杂诗》

回首毕业后在 TME 的时光,我得到了很多成长。第 1 年做在 Q 音做绿钻活动、第 2 年在直播做 Flutter 框架、第 3 年做 3D 元宇宙、第 4 年在 K 歌做游戏渲染引擎,接触到的业务每年都在变化,而每一年我也都在尽自己的最大努力在这些业务中得到成长——2 年 T9,2 年半 T10,4 年半 T11。但说实话,期间在第 3 年看不到成长机会的时候就想离开了,因此在去年提了一次离职,但总监给了渲染引擎这个核心项目让我负责,我也抓住了这个机会晋升到了 T11。但是我的喜悦之情也只有得到晋升消息的那个晚上而已——得到了,然后呢?继续成长,剑指 T12 吗?

突然间察觉,我似乎将我所经历的这一切,当成了一场有倒计时的生死游戏,而那个倒计时的终点,是 35 岁。因此我需要在那之前的每一年,努力打怪升级得到成长,以便在尽可能早的时间内晋升到高职级转管理。到目前为止,我在这局游戏中完成地很出色,甚至在可见的游戏结局里,我也有非常之高的概率达到所谓“游戏胜利”。

但我选择中途退出,因为发现在这局游戏中我后续要做的事情并没有什么意思,我将得到的并不值得我为之付出的生命能量。既然如此,那么再继续玩下去只是浪费时间,我应该主动寻求改变。

前两天有一篇博客,一个优秀的 Google SRE 工程师工作了 9 年之后决定离职,她认为自己虽然得到了对等的报酬,但是仍有一些遗憾。她早就察觉到自己并不适合这份工作,但是只是被动的等待改变,没有更早地转变角色去探寻新的可能性。

失去可能性只是一方面,更关键的是自身会陷入到了一种舒适的循环之中,大多数人会习惯在舒适圈中增长自我,但舒适圈本身会消磨殆尽个体的主动性和可能性。

此外,类似于追求多巴胺不断强化愉悦回路的行为,人生不能陷入“增长成瘾”的境地,不能将所有的成就、人生的快乐都依赖于自我的永远上升。那么无限膨胀的欲望会吞噬自己,直到某天自己的能力再也无法满足欲望,那么价值感则会瞬间崩塌,化为无尽的虚无与痛苦。

每年写完年终总结之后,我都会给自己画一个五年计划,规划未来五年内我所有可能的目标和人生路径。如果想要成功的人生,那就必须每隔一段时间,就把人生推进到下一个阶段,完成前一个目标之后,再朝着全新的目标努力。现在,是更新路径的时候了,比我预期的来得更早,在这个盛夏中来了。

本节的最后,像 Postmortem of my 9 year journey at Google 这篇文章一样,给这段职业生涯来一个分析报告:

介绍

23 岁的研一暑假,我在校招实习时加入腾讯音乐,那个时候 TME 还属于腾讯的 SNG。

虽然在那之前我已经是一个在 GitHub 活跃了 5 年有余的独立开发者,但是我从未参与过企业级的项目。通知这次实习,我掌握了项目的完整流程,也学习到了对线上的敬畏、对用户个体的关注。

加入时的期望

在实习之后,我在对鹅厂的这段经历充满热情,加上团队氛围很好,毅然选择留下来,也没有去看其他的机会。

我想在这里收获尽可能多的技术和成长,想与尽可能多的鹅们建立链接——想体验积极向上的团队氛围,想收获一段独特的人生经历。那时,我对自己的职业规划很简单,三年内到 T9 就可以了。

那个时候给自己保存了这句歌词,感谢过去的自己:

职业经历

实际的经历则曲折很多,我加入互联网行业时恰好是动荡期,立马经历了几年疫情,之后又是裁员浪潮。我的业务和团队在五年间不断变化,我也积极在其中寻求成长的可能性。

这段期间,我收获了:

  • 充足的报酬,以及成为鹅厂员工的满足感
  • 软件工程技能,现在基本上任何一个需求抛过来能立马抛出它的实现方案
  • 问题处理能力,在问题定位上形成了自己的一套方法论,得到同事之间的认可
  • 高效、有序的规划能力,繁杂的工作培养出来的业务能力,最多同时手上并行七个需求仍然井井有条
  • 坚定的工作态度,培养了在工作时保持专注的能力,在工作中几乎能付出百分百的精力和责任感
  • 钻研的精神,对各端技术都有较为深度的理解,始终保持热爱和好奇
  • 沟通与协调能力,越到后期越注重这个软能力项,在过程中负责过紧迫的大项目,同时协调将近 10 个团队配合项目工作
  • 数据分析能力,生涯中培养了对业务的敏感度,对基本数据有直观上的归因能力,在更深的挖掘能力上也得到了培养

但也有我不满意的:

  • 对大厂开始祛魅
  • 团建假期没有了(PS. 后来发通知今年恢复了)
  • 没有机会培养管理经验,个人成长机会有些不明朗
  • 等等其他不说了

自我反思

哪些地方进展顺利:

  • 我晋升很快,两年 T10、四年 T11,绩效让我几乎一直走绿色通道,三次答辩也是 Top1,保持着百分百的通过率
  • 以 T11 作为这段经历的结束已经超预期了
  • 收获了很多出来后不知道有什么用的奖项
  • 负责过 4 个核心项目,而且分别在不同的技术领域
  • 软技能得到了培养和提升,包括沟通、汇报、技术分享等

哪些地方做的不好:

  • 主动性在后期逐渐减弱,工作激情在身边人逐渐离职后开始消磨
  • 推动性较弱,顾及形象,但这点后来想通了,如果都是为了同一个目标奋斗的话,那么对事不对人就好

幸运之处:

  • 初期团队的包容和友善
  • 和设计师合作,写了很多专利
  • 绩效不错
  • 领导们也都很好
  • 接触到了很多不同形态的业务
  • 职业生涯中有半数以上的时间专注技术
  • 得到机会接触并负责不错的技术项目,包括跨端项目以及后续的渲染引擎项目,有机会负责 iOS 项目和 C++ 工程,在前端领域得到了扩展和转型

如果重来,我有什么可以改变的:

  • 做更多的技术输出,目前个人的技术输出偏少了
  • 付出一部分精力在工作之外的技术探索上

接下来,谈谈我对短期未来的规划。

1.3 未来

劝君莫强安蛇足,一盏芳醪不得尝。——《有感》

关于未来的路,我有很多问题想要去探索:我们能否真正理解他人的感受?每个人内心深处的记忆,都是真实的吗?还是说我们只愿意相信自己所看到的东西呢?意识的结构是什么?知觉是如何给予事物本质的?主体如何在生活世界中构建意义?为什么我们总会情不自禁地交浅言深呢?我们的生命,真的能够像日出那样,向上而且温暖吗?又或者只是我们心里的美好祝愿呢?

我心里还有数不清的问题想问,我愿意让自己的人生一直沉浸在对这些问题的探索之中,探索人生无尽的无尽的可能性。而眼下,我决定先去尝试实现一个多年间的想法,希望它能真正地帮助到人们去解决一些问题。

在去年的年终总结《2023,逃离仿徨》 列举到了我通过写作帮助了到一波读者的例子:

之前听闻“婴儿最快乐的时刻就是发现他自己可以对周围的世界产生影响”。在写作这种创造性的活动中,或许我也是在追求某种“存在感”,这种存在感可能就是我对世界的一点点影响和改变。而我想创业做的产品,也是具有这种属性和价值的。

但当我说出我正在着手准备做项目创业的决策时,周围人基本对此持有悲观态度,而我却清楚地知道,眼前这条路我非走不可。如果只是因为眼前这件事困难很大、或是收益很低而选择不去做,那并不是我的行事风格,我一直喜欢去做难而正确的事情,虽然这种行为在别人眼中,这可能并不正确。

在 2018 年的年终总结《2018,沉淀初心》中我提到和两位 VC 的见面,当时我的决定是先不创业,而是去大厂磨砺几年,大约 3 年到 T9 左右再离开,因为当时的我虽然想法很多,也有非常想去做的事情,但我知道以我当时的能力和阅历不足以支撑我做成它,这也是我初心。《2018,沉淀初心》 这个标题的坑埋了很多年,在这里呼应回来了。现在时机成熟了,是时候行动了,无论最后是成功还是失败,我都不悔初心。

有人觉得我固执,有人觉得这无法理解、也有人觉得我不合群,而我反思了一下,觉得这一切应该来自于我此生最大的幸运——在恰当的时机体验了死亡觉察。在《再见了,我的大学》里提到过那两次住院和一次濒临死亡的体验,那之后的每一天我都认为是新生的恩赐,我希望能尽可能地利用好自己的每一天去探寻自己存在的价值。

三年前,我将相关的想法撰写成了一篇短文《谈谈存在的价值与人生体验》,后在少数派上发表,得到了很高的热度和留言。而在本文中,我想结合工作这个主题,从另一个角度再来谈一谈生命意义这个议题。

弗兰克尔提出了三种发现生命意义的方式,分别是工作、爱和勇气,后文将围绕这三个点分别对应的去谈一谈我眼中的鹅笼、幸福和勇气。

二、鹅笼、幸福与勇气

2.1 鹅笼

笼亦不更广,书生亦不更小,宛然与双鹅并坐,鹅亦不惊。——《阳羡书生》

鹅笼一词并非是讥讽,而且来源于《阳羡书生》中一个意象,象征着一种束缚和限制。原文中的货郎在背负鹅笼的过程中,实际上反映了现代人生活中面临的各种束缚和压力。改编动画短片《鹅鹅鹅》的导演胡睿提到,鹅笼代表了人生中的种种限制,暗示人们在追求自由和理想时,往往会被现实的桎梏所困扰。因此,鹅笼不仅是物理上的容器,更是幻想与现实交织的空间。“书生从笼中吐出美食和美女,展现了人们内心深处的渴望与幻想,这种幻想的实现与现实的冲突,反映了人们在追求美好生活时所经历的挣扎与失落。”

鹅笼因此成为了一个充满神秘色彩的象征,代表着人们对理想生活的追求和对现实的无奈。在本章中,我力求还原鹅笼的本来样貌,将个体的挣扎与追寻呈现出来。

工作的目的

大部分中产阶级的打工人在工作困顿之际难免反思到工作的目的究竟是什么,而得到的结论普遍为以下几种:

第一种,为钱工作。

产生这种想法的原因有几种:

首先是社会因素,在现代社会中,我们往往会通过工作来获得社会认同,因此收入的高低直接影响个体在社会中的看法,这种文化促使我们将工作和金钱联系到一起,甚至将收入的高低视为衡量成功的标准之一。当以前的同学或者老家的亲戚知道我在大厂工作,大部分都是羡慕的,而这只是因为收入高。

其次收入可以用来满足主观的物欲,在消费主义肆掠的年代,工作的目的转化为了消费,为钱工作的驱动力在于对物质欲望的无止境追求。

因此大部分人,即便在被 PUA 或是一直在抱怨自己的工作不好、即便严重透支了自身的身心健康,也不愿意换工作。当我听到这些抱怨时就会劝其直接跑路,但对方大多只是嘴上说说,当我进一步问其跑路之后想去什么公司时,得到的答案却是换一家薪资更高的公司。为钱工作的鹅笼牢牢束缚住了他。

第二种,为未来工作。

前文提到大厂基本上都是倒计时的生死游戏,而大多员工的唯一目标就是在倒计时终点来临之前赚到尽可能多的钱,以实现财务自由、提前退休,这一路径在过往的十几年的互联网行业里已经验证了是极具可操作性的。

这种场景表面上看和第一种一样,但其实思考得更进了一步,至少知道自己想要什么。同时为了实现这个目标的未来,个体也会不断在游戏中鞭策自己晋升成长。在此期间,无论吃多少苦、忍受多少痛,哪怕工作无意义、只是拧螺丝,也不会有过多怨言,因为大家心中有一个美好的愿景,因为大家知道这些苦和痛,都是为了以后的甜。吃更多的苦,收获更多的甜。

在这种恶性循环之下,工作的唯一目标,转变为了不工作。

第三种,为了不工作。

在经历了前面的阶段之后,劳动已经发生了异化。怀有这种目的的劳动者将出卖劳动力与时间的工作,视为获取幸福生活的一种代价,而非幸福生活本身。

什么是异化?苏珊·桑塔格曾经在《论摄影》里面有过这么一句话:“人们患上了摄影强迫症:把经验本身变成一种观看方式。”随着摄影技术的普及,人们越来越倾向于用拍照的方式来经历和记录生活。拍照成为一种强迫性的行为,仿佛只有通过照片,一次经历才算完整,一个事件才算真实发生过。人们开始把现实生活等同于照片中的影像,于是旅行不再专心看风景体验人文,而是专注于拍照、打造朋友圈中待分享的照片。这种情况下,摄影这个行为扭曲人们对现实的理解,让观看变得肤浅、经验变得虚幻。而这实际上便是一种异化,工具本身变成了目的,使人疏离了真实的生活。

这种场景下的工作也是类似,将不工作作为工作的目的,忽视了工作本身可能存在的价值和意义。

那么,究竟什么样的工作目的,才是积极健康的?

前文提到了弗兰克尔提出的三种发现生命意义的方式,第一条便是工作——我们通过从事有意义的工作来找到自我价值。我们要思考在个体的创造中,个体完成了什么或者是给世界贡献了什么。

我之所以会选择编程作为职业,最主要的原因是编程的创造给我过最纯粹的快乐,无论过了多少年,我依然记得刚接触编程那会,笨拙地操弄着 VB 开发做到深夜,当开发完成、打开软件界面的那一刻,那种油然而生的欣喜永远也不会忘记。

其实早在初中的政治课中,我们就学习过劳动是为了创造社会价值、实现个人价值,而长大之后,我们似乎将其看作了一个笑话。这并不是个体的过错导致的认知偏差,而是受到了畸形的社会规训影响。

世俗的成功

世俗的成功给我们定义了一套人生模版,在《幸福的积分》一文中有过生动的描述:

对于出生于改革开放后的一、二线城市的中国青年来说,这个典型模板大体包括:

  • 拼命求学到至少本科;
  • 加入金融或科技互联网等高增长行业;
  • 在 GDP 前十的城市购买一套住宅(上海、北京、深圳、广州、重庆、苏州、成都、杭州、南京和武汉);
  • 在 30~35 岁之间结婚并生子;
  • 每年 1~2 次的境外旅行或过上同等购买力的“中产生活”;

在我身边的绝大多数人目前也都遵循这一成功模板来构建属于自己的幸福生活,而它的终局只能是幻觉。

“在过去,人们像磨盘上的动物一样追求着前方的胡萝卜——总有一些人会得到胡萝卜,但重点在于让所有人都相信朝着一个方向走,会有胡萝卜。胡萝卜更像是一种终极想象,谁吃到胡萝卜并不重要,重要的是只要有一根胡萝卜,便可让无数的人对此趋之若鹜。”(《幸福的积分》)

在《规训与惩罚》中,福柯指出这种规训在社会中具有着普遍性,称之为微权力。这种规训微权力通过规范来控制个体的行为,让社会形成了一种全景敞视的监狱,个体在这种权力结构中被迫自我规训,成为合格的社会成员。

这种世俗的成功在很大程度上,定义了我们的行为方式。它们告诉我们哪些行为是正常的,哪些行为是不正常的。这种规训不仅决定了我们的行为,也在很大程度上决定了我们对于他人的价值判断,使得一些行为被贴上了“应该”的标签。在某种意义上,社会规训的存在,是对我们的自由选择的一种剥夺。他们定义了我们“应该”做的事情,而忽略了我们其他可能的选择。

这便是福柯在《词与物》所说的“人之死”,即人之主体性的丧失,我们失去了人之所以为人的自由和无限可能。

消亡的主体

现代社会的结构使得人们为了获得社会的认可与世俗的成功,不得不将自己物化来寻求他人的认可,从而表现出一种在规训之下的虚假个性。人们在同一性逻辑下的挣扎,进一步加剧了个体主体性的消亡。

而主体性的消亡会导致失去认知自己的机会,如果一个人从未真正替自己做过决定,那他就很难有动力去向内探索,从而失去了解真实自我的机会。这也反应出现代人的困境——无法听从本能知道自己必须做什么、无法根据传统了解自己应当做什么,也不知道自己想要什么。

身边有朋友和我交流时倾诉过,即便他已经知道当下的工作不是自己最想做的,或许饱含了抱怨和痛处,但也暂时不愿离开,因为离开了也不知道自己想要去做什么。这种情况下很多人会选择在工作中进入半躺平的状态,在工作之余分出一部分精力探寻自己的副业,之后等待合适的时机进行转型。这种做法是折中的,是对微权力的反抗,也是积极向内探寻的。但我不愿意这么做,因为我认为无论做何事都得认真拿出 100% 的精力去对待,这才是认真地活过。

除却这一部分积极向内探求的路径之外,弗兰克尔提出,对这样的主体性消亡价值危机有最常见的两种的行为反应——从众,即做别人做的事情,以及顺从权威,即做其他人想要自己做的事情。

而如果很有幸,你知道自己想做什么,但可能也因为诸多因素身不由己无法付诸实施去行动。做自己很难,“这个世界日以继夜、竭尽全力让你成为其他人,如果你想做你自己,就意味着要打一场最艰难的仗。”(E·E·卡明斯)

——“可是宝贝啊人生又何止这样?我们在世上是为了感受阳光。”

工作与生活

悉达多有过这样的反思:“所有这些他做的事情无非是游戏,这游戏令他快活,偶尔让他愉悦。但是真实的生活却擦身而过,无法触及。”如果把人生模板或是职场晋升当做成游戏的话,那生活又置于何处呢?我的博客中经常有这样的疑问,我是“如何将生活工作都安排的井井有条的”?

如何平衡工作和生活之间的关系一直以来都是个大难题,我的破局之法是”认真体验”。工作的时候就拿出百分百的干劲去工作,而剩余的时间则认认真真去生活。而可以拿出百分百的干劲去工作的前提是,我工作的目的是想得到成长,以便未来能有能力实现个人价值。

因此,工作和生活保持平衡是一个狡猾说法,它的隐藏含义是将工作与生活置于平等的定位。而生活是一切,工作只是工作,后者是为前者服务的手段而已。若想保持所谓的平衡,其实更多的是想办法将两者进行恰当地融合罢了,意义本在生活之中。追求幸福的生活才是每个人的目标,职业只是实现这一目标的手段。

或许未来社会会如同《工作、消费主义和新穷人》所推演的那样,未来的工作会被赋予了美学价值,工作被视为一种个人体验和享受的方式,工作与个人兴趣和娱乐之间的界限逐渐模糊,工作本身能够带来愉悦和满足,而不仅仅是为了生计。这种情况下工作不仅仅是经济活动,更是自我实现和个人表达的途径。那些能够将工作与个人兴趣结合的人,往往才会成为最大赢家。

如何破解「鹅笼」之局,我将答案交给「幸福」和「勇气」。

2.2 幸福

由爱故生忧,由爱故生怖。——《妙色王求法偈》

应对失去的焦虑

进入幸福之前,先讨论第一个问题,我们应该如何应对焦虑?

首先要厘清焦虑的本质是什么,在快速变化的社会环境中,未来的不确定性成为我们焦虑的重要来源。有句话说“焦虑的反义词是具体”,具体则意味着决策,而决策的本质在于我们舍弃了什么,而不是选择了什么。

因此,放弃始终伴随着决定,人必须放弃其余的选项,通常放弃之后,它就再也不会回来了。但决定是痛苦的,因为它们意味着可能性的限制,而人的可能性越是有限制,就越不自由。人生的可能性,在做出决定的瞬间就消失了,这便是人生可能性的坍缩。

我们设想一种情况,如果你不向任何一种人生迈出一步,那么就相当于拥有了无限的可能性。就像《瞬息全宇宙》中杨紫琼饰演的妈妈那样,之所以她可以被选为对抗 BOSS,是因为她是所有平行宇宙中最无能的那个,因此她才拥有成为任何更好的她自己的潜力。亦如《德米安》中说的那样:“每个人其实都有变成人的无数可能,但只有他了解到这些可能性的存在,甚至有意识地去认识这些可能性时,他才真正拥有它们。”

但是时间一直在滴答滴答地流逝,不停流逝的时间将我们推向一个个待抉择的事件,我们不得不做出选择,而选择则意味着放弃另一种可能性,如此便产生了焦虑。《生命不可承受之轻》里米兰·昆德拉说,"人类的时间不是循环转动的,而是直线前进,这就是人类为什么不可能幸福,因为幸福是对重复的渴望。"

那么,我们究竟该如何应对?最近风靡全球的《黑神话·悟空》里,游戏临近结局时,老猴子给出了答案:

老猴子:古往今来的奇才异能之士,何其之多,但真正成就不朽功业的,寥寥无几。你们可知为何?

八戒:世道不公?

老猴子:世道从来不公。

八戒:运气不好?

老猴子:运气只是强者的谦辞。

八戒:那只能是…没我老猪英俊了。

老猴子:因为空有天赋,不思进取,小富即安,沉迷享乐。想安逸,又想名利;想快意江湖,又想成佛作祖,哪有这样的好事?身本多忧,怎可全求?

身本多忧,怎可全求?我们生命当中,每一天都是新的,每一秒都是新的,可是再想想,如果用这么宝贵的生命,往那个鹅笼里钻,去担心自己的焦虑,真的值得吗?失去了又能怎么样呢?以我有限的人生经历来看,往往事后来看失去了也不会怎么样,我还是我。无论有没有外在的那些东西,都要相信自己是 100% 的珍贵,我永远拥有自己的价值,没有条件。

由爱故生忧,由爱故生怖。一切的因缘际会,都是无常的,难以得到长久。人生在这个世界上会产生焦虑事件,而生命的短暂就像早晨的露水一般转瞬即逝。古人曾经形容过,人这一生就像江中的浮萍,在浪花当中摩肩接踵,你碰碰我,我碰碰你,一起归入大海。

浮萍一世,我有责任把仅此一次的人生过好,《牧羊少年奇幻之旅》的作者保罗·柯艾略在《悉达多》作序时写了这样一句话:“然后我又想到悉达多,他执意将自己投入生命本质之真实去找寻自我的道路,那天早晨我深吸了一口气,我要体味这世界所包含的一切气息,我发誓,我要选择生命。”

寻找稳定的内核

“你的心灵就是整个世界,然而,真正的自我究竟在哪里?”——黑塞笔下的悉达多历经一生都在追寻他内在的阿特曼,那是一种称之为心灵或者内在自我圆融统一的境界。他说人必须找到自我之内在的源泉,人终其一生必须拥有它。人的使命不过是回到自我,寻找自我的途径,依托于稳定的内核,那是一种平静圆融的生命体验。

在本科毕业前夕的某个深夜,我写了一篇《再见了,我的大学》,在文章里曾提到过这种内核。

那段时间处于人生低谷,身体上也不堪重负,考完研第二天发高烧,直接去医院躺了一个月。在医院的那个月,每日无所事事,思考了很多很多。当时的我在读《新原人》,冯友兰将人生境界划分为四个层次,在说到自然境界时,他提及生活在这一境界的人往往缺乏自觉意识,无论人生是漫长还是短暂,都是在“顺习而行”,如同前文提到的从众或服从权威,对自己的生活行为没有什么觉解。我在反思自我是否在一直在“顺习而行”?我是否有自己的热爱?如果有,那我能否做到心无旁骛去追求?至今为止,我失去的和我获得的,究竟存在什么样的关系?

一切想至洞察清明,豁然走出低谷——“其实,人生本着内心的宽容和洞察的清明,在外在上随遇而安,不与世争,这样的话,我们才能把每个当下活得更好,才能把人生整个的流光以一种从容的姿态安详走过。”“在此刻,一切的一切,都处之泰然。我所获得的一切荣誉,不足挂齿。我所面临的失败,也不足为惧。我所得到的,也就是我所失去的。”

柏拉图在《理想国》中写道:"我们一直寻找的,却是自己原本早已拥有的;我们总是东张西望,唯独漏了自己想要的,这就是我们至今难以如愿以偿的原因。"

但在客观上,世间亦有许多无可奈何之事,我们没有能力和勇气去改变它们,那就坦然接受并从中寻找内心的平和。如同《庄子·人间世》云:“知其不可奈何而安之若命,德之至也。” 人是一根会思考的芦苇,帕斯卡在《思想录》中用芦苇比喻人类很脆弱,一折就断,但是人类的思考却能包含整个宇宙,故人类是即渺小又伟大的存在。人类不管多么悲惨,都要努力找寻属于自己的稳定内核。

所谓“眼存星海,心怀宇宙”,便是像悉达多顿悟时的体验——“所有这自古有之的一切,悉达多一直熟视无睹。他从不在场。而现在,他归属其中。流光魅影在他眼中闪耀,星辰月亮在他心中运行。”稳定的内核始于圆融,在生命中的每个瞬间,通过圆融统一地思考,方能获得稳定的精神内核。

细流涓涓不绝,其水滴滴各异,泡沫浮于淤泥,且消乎且结乎,概无久存之例,世间人事,鸟兽栖息,亦不如此。

2.3 勇气

鸟要挣脱出壳。蛋就是世界。人要诞于世上,就得摧毁这个世界。——《德米安》

面对不确定的勇气

在追寻幸福的道路上,愿你有面对不确定的勇气。

面对一个很不稳定的世界,要坚定、勇敢地建立自己的附近,不要牺牲日常。如果我们太过强调环境的不确定性,实际上等同于放弃了我们的自主权,从而毫不犹豫地屈服于环境支配论——都是目前大环境不好、经济形式不行,导致我无所适从。

然而,真实的情况却是我们所处的环境,往往是由参与其中的个体共同塑造的,尤其是我们自己。

我们可以确定,成就不在于外在定义,全看是否按自己的意愿在活。

我们可以确定,现实的、小群体的社会交往可以把握,周围人的情感支持可以拯救我们对世界的不知所措,做一个善良的人,做一些对自己、对别人、对这个世界有意义的事情,在这个过程中与自己、与他人、与世界建立联结,从而感到幸福、满足、愉悦,这总是一个不会错的策略。

我们可以确定,如果大环境无法改变,如果命运无法抗争,那么人类最后自由就只剩下自己对生活的态度了。所有东西都能被抢走,除了一件事,人类最后的自由——去选择在当前环境下自己的态度,选择自己的方式。

今天来看,一切坚固的东西似乎最终都会云烟成雨。然而,正是这个价值观缺乏依托的时代,没有身份的束缚,反而赋予了我们选择和行动的自由。你不等于你的过去,也不等于你的现在,你永远拥有策划和改变现状的能力,也永远拥有展望未来的能力。事实上,在一个一切都在快速变化的时代、在一个除了自由别无选择的时代,我们需要勇敢起来。

在毕业前夕我拿到了 WWDC 奖学金,参加了梦寐以求的 WWDC,归途时我写了一篇《WWDC19 游记》,在这篇文章的结尾里,我写了这样一段话:

“我也曾有过这样的时光,毫无理由地深信自己的未来充满可能,相信自己将有一个玫瑰色的未来。

但更多的时候面对陌生的事物时,面对仰望以及羡慕之情时,会迷茫,会不知所措。

无论上到哪一层台阶,阶下有人在仰望, 阶上亦有人在俯视。抬头者自卑,低头者自得,唯有内省,才能看见真正的自己。

每个人都需要知道什么是自己想要的,知道什么是不可逆转的,知道用什么方式实现梦想, 知道用什么心情面对苦难,进退得失不离不弃也就都有了答案。

然而这一切其实都很难很难。我不知道面对苦难时应该何以面对,我不知道用什么方式去实现梦想,也不知道自己会错过什么,会留下什么遗憾,我不知道命运会把我带向何方,但我相信,只要面向阳光奔跑就会把阴影甩在身后。

之前 IT 之家专访让我留个寄语,我说道: ‘去学有意义的知识,去做自己喜欢的事情,做对社会有贡献的产品。’

其实这也是一年前我给导师的一封信中说过的话,那封信中我决定接下来不再继续读博了,想要选择去探索更多新鲜的事物。但今天突然觉得,大概人生最大的意义就是用余生去找到那些最爱吧。

我觉得这个世界美好无比,满树花开,柳枝摇曳。阳光席卷城市,微风穿越指间。这个世界是美好的,值得为她奋斗,也值得为自己所热爱的事物奋斗。”

在不确定的世界,坚持做确定的自己。愿你旅途漫长,充满奇遇。

甘于平凡的勇气

在不确定的世界中,愿你有甘于平凡的勇气。

很多人常常被人诟病“不务正业”,许多擅长的技能、喜欢的爱好,仿佛只有转化成物质,才是有价值的。但人生本不应该如此,“如果天性是蝙蝠,你肯定成不了鸵鸟”(《德米安》)。

我们的生命是属于自己的,所度过的每一分、每一秒,都是自己切切实实的人生。如果人生不能以喜欢的方式度过,那又有什么意义呢?拥有一个爱好,有自己想做的事情,哪怕它再小众、再“不务正业”,都是我们生命的光辉。

“铅刀有干将之志,萤烛希日月之光”。我们要做的,是去延伸它,发展它,从中获得意义感、目标感、成就感、价值感和支撑感。

具体来说,当我们准备做一件事时,无论这件事情大小,无论这件事在社会评价体系中的重要性如何,郑重地将自己全部投入进去,不计得失地投入自己的才华、注意力和时间,就会从心底感受到一种愉悦和对自己的肯定,我想这就是幸福。往往此时事情多半会做得很不错,但这也只是“幸福”的副产品,并非我们做事过程中时时关注的目标。弗兰克尔说,人所需要的“并不是一种没有压力的状态,而是值得为之努力奋斗的目标”,“这是人之为人的特征,人总是朝向自身以外的某种东西。”

那么,真正的勇气是什么?是基于健康、稳定的自尊基础上,对自己能力边界的清晰认知,并且有意愿去做一些困难的事情、做一些自己擅长的事情、做一些只有自己可以做到的事情,并对此抱有使命感,实现自己的持续成长。它不是无所畏惧,而是愿意相信:未来是好的,我是有价值的,一切都会好转。勇气不是不惧怕黑暗,而是愿意向着朝阳前进。

“勇敢的时刻已敲响我们的钟”,旅途漫长,愿你赤诚且勇敢,自由且尽兴。

告别盛夏的勇气

在漫长的旅途中,愿你有告别盛夏的勇气。

找到自我,固守自我,沿着自己的路向前走,不管它通向哪里。当感到胆怯的时候,想想纳瓦尔说的“99% 的努力终将白费。”认清这个现实,如果害怕失败,安于现状,什么都不做,不去寻求任何改变,那就不可能找不到那 1 % 的可能性。

在 Bilibili 2024 跨年晚会的结尾有一段祝酒辞,最后的那杯酒是敬自己:

我想跟自己干杯,也是跟每一个热爱自己的你们干杯。

希望以后不管遇到什么事情,在新的一年,我们都一定要在心里默念:

我一定能够遵循本心,

我一定能够热爱万物,

我一定能够追寻梦想,

我一定能够成为我想成为的那个人。

敬自己!

敬自己,希望告别这段盛夏之后,依然拥有无限春天。

——“你骄傲地飞远,我栖息的夏天。”

于 2024 年 8 月 31 日深夜。

Airing 开通了一个咨询服务

我将提供以下两种方式的咨询服务,若希望与我取得联系,可以仍选其一。

方式 1:信件咨询

非常欢迎你以此方法与我联系,我期待与读者建立良好的互动关系,也非常愿意倾听他人的内心世界和独立想法。若你有任何困难,可以直接邮件联系我,我愿意输出自己的想法,希望能给予你一些启发或帮助。当然,仅仅只是交朋友的来信也是非常欢迎的~

报酬:无偿。 但在充分保护来信人隐私的情况下,信件内容可能会在我的博客中作为案例分享出去。

回信时长:回复时长以收件时的自动回复为准,目前月均约会收到 2~4 封咨询来信,回复间隔约为 14 天。若后续信件增多,或增加回信时长,具体以邮箱自动回复为准。

咨询方式:邮件至 airing@ursb.me 即可。

方式 2:有偿咨询

我会提供时长 50 分钟的一对一咨询服务(仅限中文),

根据目前我掌握的知识领域,我可以提供以下内容的答疑解惑:

  • 职业发展规划与建议
  • 软件技术相关咨询与即时技术顾问
  • 求职/求学途中遇到的任何困难
  • 写作的有关建议与心得分享
  • 效率类工具使用建议与指导
  • 其他任何闲聊

请于 GitHub Sponsor 页进行购买 https://github.com/sponsors/airingursb,该咨询属于一对一私密咨询,完全守护您的隐私,未经您的许可不会将任何对话内容进行二次分享。

建议价格:55 USD / 400 CNY,选择 One-time 选项支付。 若无法在 GitHub Sponsor 进行支付,可先邮箱联系我 airing@ursb.me。

支付成功后,系统会将咨询预约网址发至您的邮箱,您亦可点击 https://cal.com/airing/50-min-meeting 提前查询可预约的时间。

如有任何问题可随时邮箱联系 airing@ursb.me,期待与您建立联系。

月刊(第26期):旅途

本篇是对二〇二四年四月的生活记录与思考。

因为机票相对便宜的缘故,在元旦就早早定下了五一期间新西兰的旅程。而我对国外旅游几乎没有经验,甚至攻略也是临时在飞机上对着离线地图做的,未曾想到,这趟旅途可以如此丰富多彩。

现在是旅途的最后一夜,在机场等待着归途的飞机。闲来无事,凭借着回忆,流水账般记录下这次新西兰的旅途。

Day 1:飞机 - 基督城 - 中餐馆

坐了 10h 的飞机到达奥克兰,因为口语不好办理值机之后仍然不知道该如何转机,一旁的工作人员发现了我们的难处,直接引领着我们走向正确的方向。机场大多数指示牌都标有着中文,全程并没有遇到其他的障碍。

在候机时发现脚边有两只小鸟在啄食,抬头看去也偶见在室内飞翔的小鸟,突然明白了飞机上海关宣传片中「这片脆弱的土地是我们最珍贵的财富」的含义了。

晚上达到基督城之后成功租到了车,虽然和国内的车道相反,但是开起来异常简单。一方面是这里的车道较少,极少存在需要变道的情况;另一方面则是这里的车辆极其友善——从未遇到别车插队、催促闪远光、恶意超车的情况。相反这些车辆礼让做的很到位,譬如环岛前宁愿多等几十秒,也不愿在有车的情况下汇入;窄路会车时,基本都会主动靠边让行,因此经常能看到双方车辆都在靠边让行没有人愿意先通过的情况(笑)。会车时对向车辆的驾驶员基本都会主动向我招手打招呼,还有人向我比了个手枪的手势,着实有些可爱。

说到可爱,偶见路边人家的家门口摆的一些小物件——小猪佩奇的娃娃、邮箱旁的糖果盒,还有对向路牌背面的一些贴纸。

可爱、友善,这是我对这里的初见印象。

晚上在汽车旅馆办理入住之后在附近转了一圈,虽然才下午 6 点多,但天已然黑了,街道上没有行人,商铺也基本全部关门了,附近只有一家中餐私房菜在营业,而餐厅里也并没有顾客。

就餐后,老板夫妇和我们闲聊,他们是从深圳移民至此,谈到了这里的房价、教育、工作、收入、居民的生活态度、还有我们自驾时的注意事项,涨了许多见识。

Day 2:羊驼 - 海湾小镇

第二天的行程安排在基督城周边,睡前在网上预约了羊驼牧场的参观,驱车前往,一路上的平原、牧场、 山景应接不暇,秋意甚浓。翻过最后一个山头时,看到了海湾,那一瞬间觉得这里不愧为「被世界遗忘之地」。

羊驼牧场有一位中国的员工,她分享了她在牧场喂羊的经历、介绍了每一只羊奇奇怪怪的性格特点,如数家珍。如今回忆起来,羊驼牧场在这趟旅途可以排得上 Top2,和一群大羊驼、小羊驼互动,欢乐的氛围洋溢满了整片山谷。

下午在海边的阿卡罗拉小镇消磨时光——泛舟、海滩上看景发呆、吃冰激凌、散步,直至日落才回旅馆。这个小镇人很少、海湾没有风浪、节奏很慢,我很喜欢这种静下心来感受时光在指尖慢慢流淌的感觉。

Day 3:西海岸 - 夜行冰川 - 银河

早上归还钥匙之后驱车前往西海岸,从城镇开到平原,再开上山路、经过河谷,到了峡谷,之后过了丘陵,一望无际黑色的大海映入眼帘,着实壮观。

经过 Hokitika 的小镇,天阴,大海是黑色的。再往前开去,天空放晴,海水变蓝,无比壮阔,瞬感渺小。

因为预约了 wanaka 的开飞机,所以晚上在西海岸的山路上预计多开一段,以便明天能按时赶到 wanaka。夜里开错了路,而汽车旅馆的小哥一直在等着我们办理入住(否则早就下班了)。将近九点到了旅馆,发现那栈还在为我们亮着的灯,心安定了下来,感到了一丝家的温暖——

有信号之后也收到了昨晚旅馆老板的消息,甚感温暖——

旅馆小哥和我们说这里经常能看到漫天的银河,抬头一看果真如此,一直在驱车前行,却没有抬头看看天空。

可惜没带三脚架,只能用 iPhone 的夜景模式随手拍了一张。

当浩瀚的宇宙摆在眼前之际,才惊觉自己其实也只是宇宙中的浮游。人类的存在其实与星星是一样的,都是由同一种东西构成,但我们却有着意识。在人类看向银河之时,人类的存在便成了宇宙用来了解自身的一种方式——“风景在我之内思考它自身,我就是它的意识。”

Day 4:Wanaka - 开飞机

晨起,日升,才发现昨晚的住宿环境如仙如梦。

驱车前往 wanaka,wanaka 的湖景不必多言。房东奶奶为我们办理入住的时候,即为友善,哪怕我们口语不好也耐心着听着,回复说 don't worry,一直给予我们鼓励的眼神,还给我们介绍她家的大 house,给人的感觉并不是房东,而友善的邻居奶奶。我们发现电源转接头遗失了,在 booking 上留言是否有转接头,结果房东爷爷捧着一堆五花八门的转接头送过来给我们,着实可爱。

这天还体验的开飞机——

Day 5-6:皇后镇

早餐在 wanaka 湖边散步,拍了那棵有名的树——

拿着冰淇淋在湖边走着,随手偷拍了张湖景——

午后驱车前往皇后镇,因为没定上其他项目,空出了一天,就在镇的湖边发呆——看行人、看鸭子,度过了两天。

如此闲暇,如此缓慢。看着一切素未谋面的风景,好奇的思绪在心里积攒着,总想把过客当作城市的主人,渴望在更深处的地方探索这片土地独有的个性。

即便明天我离开此地,我也仿佛根本不觉得自己离开过,空间和时间都不能影响我们,好像时空是属于我们的,而非我们属于时空。

Day 7:Deer Park - 滑板车 - 滑翔伞

这天是在皇后镇的最后一天,早上去了 Deer Park,小动物们都很给面子,拍到了不少靓照——

下午在山上玩了滑板车,这个项目意料之外的好玩。路上遇到了一个国人小哥,主动上来打招呼非常激动地和我们说这个项目多好玩,他几年前玩过至今念念不忘,所以现在又飞过来体验……

之后玩了滑翔伞,在山顶上奔跑——跳跃——起飞,以及在天空中自由翱翔的体验,是这次旅途的 Top1!

Day 8:奥马鲁 - 企鹅 - 民宿

这天离开皇后镇,驱车前往东海岸的奥马鲁,看了海豹和蓝企鹅。(蓝企鹅不给拍照,所以这里并没有照片啦)

晚上入住背包客民宿,装修氛围很温馨,厨房的墙壁上贴着旅客们的留言。

Day 9:归途

今天回到基督城,旅途开始的地方。八天前,这趟风景之旅的第一天夜晚,在此地,我感到无比的孤独。黑夜降临后,街上没有行人,商店关着门,仅有微弱的路灯宣告着这是一座城。异国、黑夜、对明天的未知、日常指导原则被剥夺的体验感、在此世的陌生感,让我感到孤独。

但经历了这八天的旅途,现在的黑夜不会再给我带来孤独感。因为我知道明天太阳还会照耀着这片大地,当阳光洒下时,这里风景依旧;因为我知道,这里生活着可爱友善的人们,遇到困难时,你不会是一个人面对。

存在的基本模式是关系,而最好的关系是个体以彼此无所求的方式建立关联。我们不是并不是作为独立的实体存在,人类是彼此关系中的生物,这里的人们用可爱与友善证明了这一点。

旅行能够让我们的生活变得更好吗?我们的生命,真的能够像日出那样,向上而且温暖吗?我不能给出肯定的答案,当我愿意想着肯定的答案奔跑。

最后,2024 年跨年夜 B 站祝酒辞中有一段话我特别喜欢,在此作为游记的结束语吧——

「我想跟平凡里的坚持干杯,我想跟那些有何不可的失败干杯,我想跟所有值得被尊重的一切干杯。敬存在。」

「我想和地球干杯,敬那些未曾谋面的山海,敬那些令人心驰神往的文化,敬那些芸芸众生们努力用爱打造的这颗璀璨星球。敬世界。」

「我要和明天干杯,我不会问你是喜是悲,不会问你有何贵干,我只会毫无畏惧地接过你手中的礼物,珍惜每一段经历。不问别离,不问结局。敬明天。」

「我想跟自己干杯,也是跟每一个热爱自己的你们干杯。希望以后不管遇到什么事情,在新的一年,我们都一定要在心里默念:我一定能够遵循本心,我一定能够热爱万物,我一定能够追寻梦想,我一定能够成为我想成为的那个人。敬自己。」

🌺 生活点滴

以下是本周期内一些瞬间的记录。

🚀 工作:晋升 T11

3 月的答辩顺利通过了,过年期间一直在准备材料,而且这大半年的项目成果也确实不错,即便这次通过率很低,但总体上还是十拿九稳,对自己还是有信心的。

T5 校招卡了一年,T10 也因为新规卡了两年,除此之外都走了绿通,三答三过,于是毕业两年 T9、两年半 T10、四年半 T11,总体来说晋升进度相对来说是比较快的了。目前 TME 前端只有一位 T12 天花板,那么下个小目标暂且先定成三年内 T12 吧。

为了梦想,继续加油!

🎬 书影音

以下是本周期的书影音记录。

  • 看完:剧集 |《追风者》| ★★★★★
  • 看完:动漫 |《物理魔法使马修》| ★★★☆☆
  • 看完:动漫 |《物理魔法使马修》| ★★★☆☆
  • 看完:电影 |《哥斯拉大战金刚 2》| ★★★☆☆
  • 看完:电影 |《海王 2》| ★★★☆☆
  • 看完:电影 |《被我弄丢的你》| ★★☆☆☆
  • 重温:剧集 |《庆余年》| ★★★★★

个人工具箱与好物分享

一直都很想写这个话题,因为本人是强烈的数码爱好者同时又是一个执着的工具控,自己尝试过很多新产品,淘汰了一批又一批产品,最终沉淀下来这套较为稳定的工具箱。 而这实际上是一个高度个人化的话题,我们每个人的观点都会受到自己生活经验和视野的影响,我认为优秀的工具或许在别人眼里却并不出色。尽管如此,我仍决定分享出来我目前正在使用的工具箱,希望能够为你带来一些灵感。 > Updated: 2024-03-29

硬件

💻 电脑与配件

  • Apple MacBook Pro (2021, 16-inch, M1 Max, 深空灰色, 32G RAM, 1T SSD) ,办公主力设备,虽然是 21 年的设备,内存也不大,但是 M 系列的芯片内存 swap 率挺高的,用起来目前没有什么问题。
  • Apple MacBook Air (2022, M2, 午夜色, 24G RAM, 1T SSD) ,移动设备,外出会带。
  • Apple iMac (2020, 27-inch Retina, Intel Core i7-10700K, 96G RAM, 1T SSD) ,公司配的设备,之后自己加装了内存,奈何 CPU 跟不上,现在办公桌上接 Luna Display 作为扩展显示器了,偶尔会跑下耗时任务。
  • Studio Display (蚀刻玻璃) ,摆在家里的显示器,后悔加钱上了蚀刻玻璃,确实会影响清晰度,且书房光线稳定镜面玻璃完全足够。
  • LG UltraFine 4K Display,2021 年企业优惠价入手的,入手一个月后又买了第二台,本身支持菊连,配合 MBP 很方便。
  • HHKB Studio,机械轴的 HHKB 很舒服,办公用。
  • HHKB Professional HYBRID Type-S (White, 有刻) ,被 HHKB Studio 淘汰下来了,目前书房用。
  • HHKB Professional HYBRID Type-S 雪,同时入了无刻的键帽作为收藏,虽然型号一样,但手感上其实比上面那款要偏软一些。
  • Magic Trackpad (银色) ,搭配白色键盘,书房用。
  • Magic Trackpad (深空灰色) ,搭配黑色的 HHKB,办公用。
  • Magic Keyboard(蓝色,Touch ID) ,从海鲜市场上收的 iMac(M1) 配件,后来没用了,还是更喜欢 HHKB 配列。

📱 移动设备

  • iPhone 15 Pro,主力机。
  • iPhone 6s,公司配置的测试机,低端机用于测性能。
  • Samsung Galaxy Z Flip4,自购的 Android 设备,用于体验 Android 软件和开放生态,偶尔作为阅读器使用。
  • Xiaomi Mi 9,公司配置的测试机,经常用来跑项目。
  • Apple Watch Ultra,个人更喜欢白钢的 Series 版本。
  • iPad Pro 12.9 (M1) ,你的下一台电脑还得是电脑,糟糕的文件系统、窗口管理和软件生态让它成为了 Bilibili 播放器。
  • iPad Pro 妙控键盘,配合 iPad Pro 使用,但不喜欢用,组合起来比 MacBook Air 还要重上许多。
  • iPad mini (第六代) ,看电子书或玩游戏较多,外出会带着,使用频率很高。
  • BOOX Leaf 2,开放系统体验不错,和 KO3 有一样的侧边翻页键很是实用,用来看微信读书。
  • Kindle Oasis 3,清晰度会比 Leaf 2 好一些,但 Kindle 退出中国市场后体验上有折损,且不支持微信读书,后续就没用了。
  • zendure 征拓充电宝(10000mAh, 踏青绿) ,体验过多款充电宝,这款价格实惠且非常小巧好看,是最满意的一个充电宝了。

🎧 音频设备

  • AirPods Pro (2nd generation, USB-C) ,之前的 1 代被洗衣机洗了两个小时,虽然还能用,但是降噪和通透模式不好使了,遂更换。
  • AirPods Max,使用频率较少,戴时间久了很压耳朵。
  • HomePod (1st generation) ,HomeKit 网络中枢,也时常播音乐。

📷 影像设备

  • 富士 X100VI,直出效果很好。
  • 907X & CFV 100C + XCD 4/28P,大道至简。
  • Insta360 GO 3,夜间画质一般,但胜在小巧,拍到比拍好更重要。

🎮 娱乐设备

  • Nintendo Switch (OLED Model) ,塞尔达启动器。
  • Nintendo Switch Lite,偶尔当便携设备带出门玩玩小游戏。
  • Steam Deck,偶尔玩玩 Steam 小游戏。
  • PlayStation 4 Pro,吃灰很久了,刚毕业的时候为了玩《战神》入的。
  • Xbox Elite 无线控制器,质感很好,配件和软件丰富,可玩性强,iPad、Steam Deck 均可使用。
  • Nintendo Switch Pro 控制器,完善了 NS 的游戏体验。
  • 坚果 N1S Pro 4K,三色激光投影理论数值无敌,但是系统不行,半年就返修了一次,摆在卧室。
  • 极米 H3S,在司内的商城用积分换的,用了 3 年了没有问题,摆在客厅。

软件与服务

📁 系统

  • Arc,浏览器,设计理念符合我的使用习惯。
  • CleanShot X,截图工具与录屏工具,标注快捷键挺好用的。
  • Bob,翻译软件,有不错的快捷键支持,用的较少,大多数情况下会用 Raycast + AI Command 翻译。
  • Stats,状态栏展示设备 CPU、内存、网络、磁盘、电源灯信息。
  • Keynote,制作平时分享或答辩的 PPT,之前体验过 iA Present,还是觉得原生 Keynote 最好用。
  • Numbers,制作图表。
  • Bartender 5,也属于 Mac 必装软件了,可以控制状态栏展示。
  • RunCat,用小猫猫的状态来表示 CPU 的状态,挺可爱的。
  • Vimac,使用 Vim 快捷键控制 Mac。
  • NinjaMouse,配合 Vimac 使用,切软件的时候会自动把鼠标指针挪到激活窗口的正中间。
  • Input Source Pro,切换软件时自动切换输入法,做研发时很常用,如切到 IDE 或终端时自动切英文输入法,而切到文档或者企业微信时则自动切中文输入法。
  • AltTab,软件切换工具。
  • Hookmark,配合 Drafts 使用,方便做任意书签的笔记。
  • Geekbench 6,本机性能测试软件。
  • AlDente,电源充放电管理软件,可以更精准的、个性化的控制电源策略。
  • Tencent Lemon,硬盘清理软件,小巧实用,希望不要变成恶龙。

🌏 服务

  • Perplexity,Arc 默认搜索已经替换成了 Perplexity,每日搜索在 20~40 次,配合自己的 Prompt 和 Claude 3 Opus,答案的输出质量非常之高,大幅提升学习效率。
  • 腾讯文档,工作用,或者需要协作的场景用。
  • GitHub Copilot,从 2022 年一直免费用到现在,大幅提升研发效率,尤其配合 Jupyter 使用,可以做到自然语言处理数据。
  • Readwise,各端阅读源摘要汇总到 Readwise 服务,之后在 HeptaBase 做笔记和二次整理。
  • IFTTT,自动化服务,支持 AI 和 Webhook,可以做各种记录的聚合、整理和转发,最终网页收藏汇总到 Cubox,而消息通知则汇总到 Telegram 的个人频道。
  • Last.fm,统计各端听歌记录,图表做的很好看,配合 musicfox 使用。

🧰 效率

  • Raycast,入了 Raycast Pro + GPT 4 扩展包,最常用的功能是 HotKey 快捷打开软件 、AI Chat 与 Clipboard History。下面是一些常用的插件:
    • AI Chat & Quick AI,电脑上一键呼出 AI,这就显得微软的 Copilot 独立按键很蠢了。
    • AI Command,根据自定义的预制 Prompt 执行复制的文本,一般我会让它提取 CI 结果中关键的信息,然后生成我需要的配置;或者让它格式化压缩 JSON;分析堆栈信息;英汉互译等等。
    • Clipboard History,之前入过 Rewind,发现完全没有必要,直接 Clipboard History 效率更高
    • My IP,研发原因经常要看内网 IP,快捷键直接查看+复制。
    • Google Search,因为 Arc 默认搜索设置成了 Perplexity,当明确想用 Google 搜索的时候会按下这个快捷键。
    • QR Code Generator,工作原因有时需要 URL 转二维码,这个就很实用,不需要打开浏览器利用插件或三方工具转。
    • Todoist,配合 Todoist 使用,可以在状态栏或弹窗显示今日任务。
    • Format JSON,有了 AI Command 之后这个插件很少用了。
    • Linear,配合 Linear 使用。
    • Scrcpy,配合 Scrcpy 使用。
  • Rize,时间统计与专注管理软件。
  • Fantastical,统一的日历和 TODO 管理工具,使用七年有余。
  • Apple Calendar,配合 Fantastical 使用。
  • Todoist,这些年体验过 OmniFocus、Things 3、Sorted3、Apple Reminder、滴答清单、Taskade,最后选择 Todoist 是看中了它开放的 API、优秀的 Vim 快捷键支持、以及对 Fantastical 等三方软件的良好集成。如果不考虑以上三个方面,综合来看滴答清单的体验和性价比是最佳的。
  • Drafts,搭配快捷键和桌面置顶功能,用作临时草稿或者桌面便签。

📚 阅读

  • Readwise Reader,和 Readwise 的集成做的非常好,体验流畅,支持快捷键、RSS 订阅、NewsLetter 订阅、也可以高亮图片。一般直接用网页端。
  • Inoreader,RSS 订阅源管理,配合邮箱、IFTTT & AI、Telegram 订阅、关键词监控做一些自动化提醒和总结。
  • 微信读书,新出的 AI 大纲十分好用,阅读完之后配合 Readwise 保存书摘。
  • Cubox,网页收藏箱,配合 IFTTT 使用,各订阅渠道最终都会收录到 Cubox 里。
  • Instapaper,因为免费+集成化做的很好,配合 IFTTT、浏览器插件、Inoreader 做网页收藏的中转服务。

📝 笔记

  • HeptaBase,笔记软件这七八年体验过 Notion、Bear、NotePlan、Tana、Roam Research、Capacities、LogSeq、Obsidian、Craft、Mem、MWeb,最后用下来最趁手并且产出效率最高的,只有 HeptaBase,除了卡片笔记之外,还有画板功能、PDF 标注、以及配合 Readwise 使用。
  • Obsidian,折腾了不少插件,但是我用 Obsidian 最后只用其中的 Excalidraw 插件,现在 Obsidian 已经变成 Excalidraw 的客户端了,是每日必用的白板工具,用作记录草稿和工作思路。

👨🏻💻 编程

  • 编辑器
    • Visual Studio Code,临时查看项目、分析日志、或者使用 Jupyter 的时候才会用到。
    • WebStorm,以前也曾是 VSCode 党,后来才发现付费软件真香(公司许可证免费),内置的 Git 、冲突处理、文件管理很好用,不需要自己折腾插件,还可以调内存占用,流畅性表现也比自己装一堆插件的 VSCode 好很多。
    • CLion,和上面一样是 JetBrains 家族的软件,敲 C++ 项目的时候会用。
    • Xcode,敲 iOS 项目的时候会用。
    • Android Studio,敲 Android 项目的时候会用。
    • Cocos Creator,编译游戏项目的时候用。
  • 编辑器插件
    • Vim,JetBrains、VSCode、Xcode 都会装。
    • MetaJump,配合 Vim 使用,快速移动指针。
    • Banish Pointer,配合 Vim 使用,焦点在编辑器的时候隐藏鼠标指针。
    • Error Lens,增强错误提示。
    • Filter Line,查日志时可以过滤关键词。
    • GitHub Copilot,辅助编程。
    • GitToolbox,JetBrains 的 Git 增强工具,可以显示行内提交信息。
    • GitLens,VSCode 会装,类似于 GitToolbox。
    • Git Graph,VSCode 会装,JetBrains 内置的足够好用。
    • TODO++ ,VSCode 会装,统一管理注释标记,JetBrains 内置的足够好用。
    • Bookmarks,VSCode 会装,书签管理器,JetBrains 内置的足够使用。
    • Jupyter,做数据分析时用。
    • WakaTime,记录编程时间。
  • 浏览器插件
    • Readwise,配合 Readwise 服务使用,可以直接在网页上划线,之后在 Reader 中阅读。
    • Instapaper,配合 Instapaper、IFTTT、Cubox、Readwise 使用,收藏网页。
    • 沉浸式翻译,阅读部分外文网站时使用。
    • 1Password,配合 1Password 使用,自动注册或自动填充登录。
    • Vimium C,Vim 快捷键进行网页浏览。
    • Whistle 规则管理,配合 Whistle 开发时使用,Proxy SwitchyOmega 的替代品,预计 2024 年 6 月 Chrome 将停用 Manifest V2 扩展,那么 SwitchyOmega 将不再可用。
  • 终端
    • Warp,开箱即用,颜值也不错,最重要的是完全不需要自己去折腾各种配置和插件,代替了之前自己使用了很久的 iTerms。
  • 命令行工具
    • oh-my-zsh,用的比较多的是 git alias、git 插件、zsh-autosuggestions 插件。
    • tig,命令行 Git 增强工具,用的很多。
    • Homebrew,Mac 必备的软件管理工具。
    • musicfox,命令行听歌工具,因此电脑上没有装音乐软件,通过 Vim 快捷键可以操作,还可以连接 Last.fm。
    • nvm,管理 node 版本。
    • Scrcpy,安卓开发时使用,一般使用真机配合 Scrcpy 投屏,方便电脑操作或录制。
  • 版本管理
    • GitKraken,一般较复杂的项目和场景下才会用,比如包括很多 submodule,或者需要溯源很长的 commit 的历史去查某个地方的变动,否则直接用 tig。
  • 网络调试
    • Whistle,工作中最常用的软件,可以查看 raw、配置代理、保存/还原现场、模拟/重放请求、拦截修改回包,灵活方便、简直万能。
    • Chrome,常规场景就是用代理软件开发本地项目,除了 Devtools 偶尔还会用 inspect 调试真机,或者用 net-log 定位更深层次的网络问题。
    • Safari,调试 iOS 真机的 JSCore 或 WKWebView 会使用。
  • 其他
    • Linear,一个 Issue 管理软件,可以给 Project 设置里程碑并展示 Roadmap,也有工作量评估和时间预测功能,可配合 IFTTT 同步到 Todoist,自行安排项目管理时会使用。
    • 控制台,抓 iOS 客户端日志用,类似于 LogCat。
    • Perfdog,性能测试软件,分析卡顿时常用。
    • Xcode Instruments,性能测试,分析耗时堆栈时常用。

🏚 其他

  • 📷 图片处理:Pixelmator Pro,好像是限免入的,软件功能强大、体验流程、操作便捷。
  • 💰 财务管理:Money Things,配合移动端使用。
  • 🔐 安全工具:1Password,统一管理密码和各种证件、license,内置双重验证工具、Passkey、SSH 代理。

手机软件

  • 📝 记录
    • 世界迷雾,把生活过成探险家模式,因为这个软件不知道绕了多少路,但成就感满满。
    • Rond,被动的停留地点记录。
    • 一生足迹,被动的移动轨迹记录。
    • Day One,日记记录,详见周刊(第19期):日记的意义
    • Daylio,每日心情记录。
    • 豆瓣,书影音记录,部分豆瓣小组也不错,每日豆瓣也会推荐,可以 RSS 订阅。
    • Money Things,之前体验过许多记账软件,最终选择 Money Things,支付计划、自动化记账、场景管理、快捷记录、开销统计做的很不错,也有桌面端。
  • 📖 阅读
    • 微信读书,一般用 iPad 或者 Leaf2 看。
    • Readwise Reader,配合桌面版使用。
    • Unread,RSS 阅读器,免费,且移动端体验比 Reeder 做的要好。缺点是没有桌面端,桌面端 RSS 阅读可以选用 Web 版或者 Reeder。
    • Cubox,配合桌面版使用。
    • 滴墨书摘,看实体书时使用,OCR 录入文字很方便,之后再导入到 Readwise 里。
  • 🧰 效率
    • Fantastical,配合桌面版使用。
    • Apple Calendar,配合 Fantastical 使用。
    • Todoist,配合桌面版使用。
    • Drafts,配合桌面版使用,但移动端快速输入体验也非常优秀。
    • Niagara Launcher,Android 机必装软件,提高首屏启动 App 的效率。
  • 🌍 网络
    • Arc Search,用手机快速查东西的时候会用它,将默认搜索引擎替换成了 Perplexity。
    • Readwise,配合桌面阅读使用,移动端则是方便定期回顾。
  • 🏸 运动
    • 健康,偶尔记录心情和体重,查看身体指标变化趋势。
    • 健身,运动时用,合并圆环的设定很好。
    • WorkOutDoors,可以导入路书,相较于健身更适合有路线的户外运动,如徒步、骑行等。
    • Grow,图表统计很好看。
  • 🛍 出行
    • 彩云天气,对雨水的实时预报较为精准,无奈免费版广告实在是太多了。
    • Apple Map,开车时偶尔会用,较高德地图而言,实时位置更精准,且可以和 Watch 联动。
    • 高德地图,之前对比过百度地图和腾讯地图,高德堵车预测更加精准、路线推荐更加合理,城区红绿灯展示也很实用。
    • 航旅纵横 Pro,航班信息齐全且更新及时,各种通知的及时性比现场播报还要快。

月刊(第25期):爱具体的人

本篇是对二〇二四年一月至三月的生活记录与思考。

鸽了两期没有更新,从过年至今为止的假期和周末一直在准备着 T11 答辩,这周答完了算是暂时解脱了,空闲的周末又回来了。

前两天参加了一个司内的 TED 演讲,演讲前一晚准备了下 PPT,当天对着 PPT 练了两遍流畅性和节奏就直接上去讲了。我一直认为演讲不需要稿子,重要的是台风稳,需要根据听众的反应去动态调整要讲述的内容和节奏。但距离我上次在台上讲话已过去整整六年,刚开始上台还有些紧张,但随着状态渐入佳境,未曾想到最终居然拿了个冠军。意外之余,更多的是欣喜。

这期内容则是以文字的形式记录下我这次演讲的主题——《爱具体的人——研发质量保障的人文关怀》(已做脱敏处理)。

爱具体的人——研发质量保障的人文关怀.001.jpeg

如果你现在面临一个选择,一边是个别的无法复现的用户反馈,另一边是亟待上线的 Feature。那么基于定语的限制与 ROI 的考虑,我想大多数人、也包括我,会毫不犹豫地选择优先做需求。那么希望听完这次演讲,大家可以对这个毫不犹豫的选择产生一点点的思考。

爱具体的人——研发质量保障的人文关怀.002.jpeg

在我们业务场景下,用户的反馈率是相当低的,当然这一定程度上是因为业务体量大、分母大,但这并不意味着用户的反馈就不值得优先关注。如果用户的反馈不能及时得到解决,那么用户会极易暴躁。就像我这前两天刚截的图,但其实还有更多类似的反馈,言语实在难登大雅之堂。那么这个时候在我看来,这个用户和产品的纽带已经变得愈发僵硬了。

爱具体的人——研发质量保障的人文关怀.004.jpeg

但其实只要认真去观察,可以发现绝大多数用户第一次反馈的言辞其实是友善且恳切的。因为用户喜欢这款产品,所以他遇到问题才会反馈。他的初衷是希望自己的问题得到解决,他希望去帮助我们改进这款产品,让它变成更好的样子。

爱具体的人——研发质量保障的人文关怀.005.jpeg

我再举一个例子,前几天我灰度了一个技术容器,监控发现有个接口报错了几次。我赶紧去查了一下,因为这个接口的报错会导致用户直接进不了游戏,发现这两天这个错误只影响到了两个用户。针对其中一个用户捞日志上报,发现出现这个问题之后,他每天早上、中午、晚上每隔几个小时都会去尝试登录游戏。很遗憾,他每次都无法登录,但他依然在尝试。我可以感受到他焦急的心理,于是当天晚上优先处理了这个问题。

在这里我不是想说这个 Bug 多严重多隐蔽什么的,而关键的点在于,这个用户从始至终,他都没有反馈过。那我们可以想象,如果这个问题一直被忽略,我们就必然会流失这两个用户。而即便他们流失了,我们在大盘上也永远发现不了这个事实。因为这个分子,微乎其微,甚至还无法在转化率曲线上留下一丁点数值上的波动。

爱具体的人——研发质量保障的人文关怀.006.jpeg

那么对于有反馈的人呢,如果我们解决了他们的问题,他们会回馈以我们感谢、表扬、鼓励。这个时候我能真正感受到屏幕后面的反馈是一个一个活生生的人,而不是 UID + UserAgent 的标签而已。其实我身边也有长辈使用我们的产品,那么想象下,当自己的长辈遇到送礼送不出去的问题、好不容易完成任务却无法领奖、想玩游戏却无法登录,我们又该会去怎么处理它呢?

爱具体的人——研发质量保障的人文关怀.007.jpeg

Altman 在斯坦福大学商学院上有一门创业课程,他里面提到了两个点我感触很深。

第一个是「感性」,他指出我们不应该以纯理性的视角去分析我们产品,不应该只关注大盘、DAU、转化率、留存、ARPPU——而应该去掺杂一些感性,以感性的角度出发去思考产品的增长。产品和用户之间的关系,应该类似于一种相亲关系、或是婚姻关系,应该去打造、维护、经营产品和用户之间长期友善的关系。

爱具体的人——研发质量保障的人文关怀.008.jpeg

第二词是「尊重」,他举了一个 WUFOO 的例子,是一个做在线表单的产品。他们团队仅 10 个人,要服务 50 万个用户,每周有 1000 多的问题需要去解决。而他们每周会安排专门的人去滚动这些问题,争取每周清零。他们对用户的尊重,值得尊重。

爱具体的人——研发质量保障的人文关怀.009.jpeg

这让我想到了我上学时用心打磨的一款产品,在反馈区中收获了用户们的喜爱、支持和溢美之词。这些反馈反哺着我对于研发的热爱、对于打造好产品的执着。以至于这么多年后我再想到这些反馈,心中仍然是暖暖的,充满信念和力量。

爱具体的人——研发质量保障的人文关怀.010.jpeg

最后一个关键词,是「意义」。最近看了一本书,叫《有限与无限的游戏》,作者将这世间人类参与的行为都比作游戏,而这些游戏又可以分为有限与无限的两种。而后者的意义感和价值感更加强烈。

回到演讲开头的那个场景,其实稍微转换一下视角来看,会得到一个完全不一样的答案。如果我们只是从外而内,逐步去推进需求,然后去解决问题,目的是为了打造好产品,那么这有一个明确的终点和可复制的周期性,是一个有限的游戏。

但如果反过来呢?我们如果是抱着打造好产品的信念去推进需求解决问题,在这种情况下的内驱力是无比强大的,从内而外地发展,这拥有着无限的可能性。

爱具体的人——研发质量保障的人文关怀.011.jpeg

说了这么多,总结一下,我们应该从真实的用户体验出发,关注当下每一个用户,友善地去连接产品与用户之间的关系。

爱具体的人——研发质量保障的人文关怀.012.jpeg

这一大段话,如果浓缩成一句话,那就是大家耳熟能详的——「一切以用户价值为依归」。

爱具体的人——研发质量保障的人文关怀.013.jpeg

谢谢大家。

爱具体的人——研发质量保障的人文关怀.014.jpeg

🌺 生活点滴

以下是本周期内一些瞬间的记录。

🏮 春节假期:月是故乡明

4111710590875_.pic.jpg
4101710590872_.pic.jpg
4091710590872_.pic.jpg

🛫 返程:在飞机上拍到了广州塔

4081710590871_.pic.jpg

🇭🇰 香港:去香港办了张银行卡

4151710590881_.pic.jpg
4161710590882_.pic.jpg
4141710590879_.pic.jpg
4131710590878_.pic.jpg

🌇 夕阳:日落黄昏后

4071710590867_.pic.jpg

🏆 突出个人与 TED 演讲

4041710590865_.pic.jpg
4051710590866_.pic.jpg

🐱 手套:依旧憨憨的

4121710590877_.pic.jpg

🎬 书影音

以下是本周期的书影音记录。

  • 读完:心理学 |《存在主义心理治疗》| ★★★★★
  • 读完:理财 |《慢慢变富》| ★★★★☆
  • 读完:哲学 |《有限与无限的游戏》| ★★★★★
  • 读完:漫画 |《亲爱的我饱含杀意》| ★★★★★
  • 看完:电影 |《飞驰人生2》| ★★★★☆
  • 看完:电影 |《一个母亲的复仇》| ★★★☆☆
  • 看完:电影 |《三大队》| ★★★★☆
  • 看完:电影 |《漫天过海》| ★★★☆☆
  • 看完:电影 |《交换人生》| ★☆☆☆☆
  • 看完:美剧 |《洛基2》| ★★★★☆
  • 看完:剧集 |《猎冰》| ★★☆☆☆
  • 看完:剧集 |《如果奔跑是我的人生》| ★★★☆☆
  • 看完:日漫 |《物理魔法使—马修(第1季)》| ★★★★☆
  • 看完:日漫 |《咒术回战2》| ★★★★★
  • 在看:日漫 |《葬送的芙莉莲》| ★★★★★

强推《葬送的芙莉莲》🎉

Capture-2024-03-17-145239.jpg

2023,逃离仿徨

又是一年总结时,自开始写年终总结以来,这已是第六年,年龄也悄无声息地往 29 迈去。在这个年龄感觉到了焦虑和彷徨,以至于生日那天都没有在朋友圈里透露半点风声,我才理解到小时候那时无法理解到的——为什么有人会不喜欢过生日。

这一年在工作上经历了彷徨和迷茫,寻求改变的过程非常曲折,而我也思考了许多、做了许多努力去逃离这份彷徨。因此今年的主题,就叫做「逃离彷徨」吧。

生活

每年的年终总结第一个环节都是工作,但是这次不一样了。今年,先写生活。

恋爱

喜大普奔,今年终于脱单了!这是今年最重要的收获了,所以第一个写。之前一直没有官宣过,因为我觉得感情的事情是私事,无必大庭广众下同外人说起。所以呢,这个环节就到这里啦~

交际

本科上铺+舍友+同组同事 Zyktrcn 离职了,带着小熊猫回了广州。说实话心里有些空落落的,自工作以来结识的朋友最后都走的走、散的散。之前的好兄弟舍友,他女友每天抱着泡芙玩,他们一起去了苏州;学习小组的 Wall-E 和 Ellia 离开了;大姐大 Sara 走的这两年,组里的重担都落在我肩上了,工作时偶尔看到以前同事 commit 的代码,总能想到那几年组里欢乐的时光。

或许真如告别酒桌上领导说的那句话——“聚是一团火,散是满天星”,大家分散在世界各个角落里,散发着光和热。

人生如逆旅,但我希望大家不会只是过客。

今年还搬了家,搬家前最后拍了下阳台的风景,依旧绝美——

旅行

今年去了不少地方旅行。

👪 大鹏:春节期间家人过来深圳过年,带他们逛了大鹏半岛。

🍺 北京:年初去的,出差去听个会,见到了很多年没有见的老朋友,在冬日北京的巷子里散步,谈笑着这些年的经历。

🐚 惠州双月湾:和女友的第一次旅行,在海边捡了很多贝壳。

⛰ 福鼎:端午请了很多天假,在深山的旅馆里修行,别有一番意境。

🐂 顺德:逛了下清晖园,清晖园附近的牛杂贼好吃,后来从珠海旅行回来时还特地绕路吃了一顿。

🐬 珠海长隆:国庆前周末玩了一趟,去了长隆新开的宇宙飞船,愉快的经历(但是胆小没坐过山车)

🎏 清远-英德-从化:国庆自驾游路线,清远漂流出乎意料得很好玩!

🌊 巽寮湾三角洲岛:10 月底部门团建,岛上物资匮乏,帆船上很容易出片。

👻 长沙:自驾去的,来回开了 20 个小时…印象深刻的反而不是长沙的美食,而是深夜开车听着鬼故事,结果在国道里真的路过了公墓。

🎰 广州:找高中同学玩,VR 打僵尸、抓娃娃、密室逃脱,非常非常非常开心!

好物

分享下今年新入的好物。

  • Steam Deck:吃灰了,但是 SD 抱在手里的一瞬间,哪怕还没有启动游戏、哪怕还没有想好要玩什么游戏,就已经感觉到很快乐了。那种游戏库整个把握在手里的感觉,简直就是童年的梦。
  • BOOX Left2:Kindle Oasis 几年,唯一的缺点就是看微信读书不方便,所以换了外观相似的 BOOX,很中意它们的物理翻页键。用了半年,中途开不了机返厂修了一次。而对比下 Kindle 的两台设备用了很多年也没有坏过…
  • Insta360 GO 3:贼可爱的小相机,每次旅行必带它,所幸直接放在车里了。挂在脖子上可以去漂流,带着三脚架可以拍 vlog,软件自带的自动剪辑也很方便,简直不要太完美。唯二的缺点——一个是夜景实在拉胯,入夜之后属于不能看的状态,晚上只能手机录;另一个是内置存储,无法扩展存储,同时导出素材有点麻烦。
  • 碧云泉饮水机:很便捷,直出任意温度矿泉水,冲茶装水都很方便。后悔知道的晚了。
  • 戴森吹风机:很好用,女友的说法是碾压其他吹风机,再也不用护发素。
  • AirPods Pro2(USB-C):之前的 Pro1 被洗衣机洗了一遍,虽然勉强还能听,但是降噪失效了,且偶尔会有杂音,趁着今年出了 USB-C 的新款换了一下。
  • 莫曼顿助力自行车:每天上下班的代步车,三档助力可调,续航 35 KM 左右,正好够我一周上下班(上坡的时候和开挂一样);不开电的时候也可以当自行车,有七档变速,缺点是不开电的时候有点难骑。

试驾

今年陪不同朋友去试驾了几辆车,谈谈我个人的感受。

  • 问界 M5:体验智驾的时候翻车了,该转弯的时候没变道成功,直接直行了。而且我不太喜欢它的内饰,有点老气。
  • 阿维塔 12:颜值是长在了我的审美上,但电子后视镜的距离感很难把握(可以不选)。没有 HUD。
  • 保时捷 718:颜值爆炸好看,但是深圳已经见到很多了,车库里的保时捷和特斯拉都能在一起玩三消了。坐姿舒适,驾驶的运动感很强,但内饰还是上个年代的,音响也就听个响,不超过 100 迈的时候开起来很抖,而且噪音太大。可惜的是没有体验到 SC。
  • 蔚来 ET5T:颜值不错,驾驶感也很棒,兼具运动和舒适。nomi 也很可爱,但 ET5 没有 HUD。同时驾驶位坐姿偏高,倒车镜挡住了 1/4 的视野,耗电也相对严重些,续航一般。但蔚来可换电,而且坐姿偏高对 SUV 车型影响也不大,后续可能会考虑再试下 EC8 或者 ES8。

运动

今年坚持打球(和女友也是打球认识的),共打了 59 场球,勉强不是个菜鸟了。号称前场雨刮器,后场也能打高远了。

还去爬了一次塘朗山。

整体而言,运动以后比以前的状态好了特别多。

情绪

今年依旧每天写心情日记,今年的 iOS 17 还出了「手记」和健康里的「心理状态」,是我一直想做的 App。

今年的情绪图如下,处理六月那段时间比较艰难之外(后文讲到工作再提),其余时间都是保持着小确幸的。

为什么坚持记录心情,这个点在《月刊(第19期):日记的意义》里已经深入阐述过了——记录是防止信息流失的方法,也是防止自己流失的方法。记录自己的生活,是为了让生活形成整体。

在「豆瓣2023年度社区故事」的介绍语里有这样一句话,很时候作为这个「生活」模块的结尾——“我们认真投入生活本身,用爱意与目光,穿透最具体的细节,在果壳做的微型宇宙里为自己加冕。”

猫猫

手套和泡芙很好,尤其是手套——每天除了睡(而且是躺尸睡姿)就是吃(而且是埋头干饭,恨不得整个头都塞进猫碗里)。

泡芙还是很调皮,但现在听话很多,喊它至少会回应你了。

手机没有存货,刚现场拍了一张:

学习

今年是大模型 AI 之年,借助 AI 和一些软件以及自己的工作流,摄取知识的效率更高了。这里简单介绍下我的工作流和方法。

在 2022 年的《月刊(第16期):个人信息流分享》一文中,介绍过自己的信息处理模式,2023 年有一些辅助性上的改动,这里介绍一下。

首先对白板工具更完善的使用——LogSeq、Obsidian、Heptabase 都支持白板和 Readwise 集成(Muse 、无边记、GoodNotes 等软件不支持 Readwise 集成,这里就不介绍了),整理标注和笔记的输出时可以使用白板辅助整理思路。工作时很多方案文档以及今年的月刊都是以这种模式进行运作的。

其次,在打草稿梳理研究思路的时候,我更喜欢用 Excalidraw,在 《月刊(第24期): 十年编程之路》里介绍过使用经历,这里不再赘述了。有时候我针对一个专题的研究能写出 200 多 MB 的图谱,但同时 Excalidraw 却一点也不卡,配合顺手的快捷键,用起来得心应手。

至于 AI 软件的推荐,首推 OpenAI Plus,之后是 Perplexity、DALLE-3、Poe。但是这几款对 IP 都有要求,可以考虑微软云服务器做代理。这里重点推一下 Perplexity,基本上它在我这里已经取代了 Google,它会结合搜索引擎的结果进行回答,同时也支持识别 PDF 文件和图像,GPT-4 的回答质量非常之高:

Perplexity 的免费版也足够使用(免费版只是不能用 GPT-4 而已),如过注册的话可以使用这个链接 → https://perplexity.ai/pro?referral_code=0ZSAD0VT ,未来如果想要付费的话可以得到 $5 的抵扣。

如果没有条件翻墙的话,可以使用 Raycast AI,目前对网络没有限制,支持 GPT-4 等多个模型,一般深度对话我会使用它(一共三台设备,这是工作电脑的 Raycast AI 的使用记录):

除此之外,它还可以通过 Prompt 自定义 AI 命令,以下是我常用的一些命令:比如提取 WeChat Bot 返回的 JSON 配置、JSON 压缩、输入 URL 总结网页内容、根据 JCE 协议转 ts interface 或者 直接 Mock 出数据以便 Whilstle Mock Response ——

当然抛开 AI 能力,Raycast 本身也是一个足够优秀的软件,工作电脑使用记录如下(通过 Raycast 打开 Arc 用了 2.3万次,打开终端 2k 次):

如果想体验,可以使用这个链接进行注册:https://raycast.com/?via=airing

还有 《月刊(第23期): 多任务中的时间管理》 一文中推荐过的 Rize,对于培养专注和免分心有一定的效果,可以使用 https://rize.io?code=291287 链接注册免费体验一个月(仅 Mac)。

还有 Arc 如今也取代了 Chrome 和 Safari 成为我主力浏览器,树状标签、自定义空间、分屏浏览、AI 重命名都是很实用的功能。不知道现在还是不是邀请制了,但可以直接使用 https://arc.net/gift/ab29c0b8 链接注册体验:

今年使用 IFTTT 和它的 AI 能力自动化了我的信息流:

  • IFTTT 注册了我在 inoreader 上的订阅更新,如果有固定的关键词触发了(比如「年度总结」),会直接收录到 Cubox 并通知到 Telegram 群里。
  • Rize 每日的专注邮件会转发给 inoreader,之后 IFTTT 监听,如果有 Rize 的邮件,就会使用 IFTTT AI 自动总结今日的时间报告发到群里。
  • 我会收藏一些优秀的文章到 Cubox 里,通过 Webhook 触发到 inoreader 订阅,之后再由 IFTTT 转发到 Telegram 群里。对于文章本身,IFTTT AI 也会给予总结。
  • 利用 IFTTT 和 Github Action 还做了每周编程报告,Action 通过 API 拉 WakaTime 的上周数据,之后 IFTTT 总结发到群里。
  • 同理,利用 Action + IFTTT 还做了对 last.fm 的收听报告的总结。
  • GitHub Star、豆瓣动态、微博动态也交由 IFTTT 订阅了。
  • 每次发布博客也交由 IFTTT 监听对应的 RSS,有博客更新也会通知到群里。

今年还体验了 Notion AI,但是没几天就弃坑了。

Notion 年底支持了 Q&A Beta,它的理念非常好,可以基于自己的 Notion 知识库进行 AI 问答。好不容易申请到了 Beta 体验资格,效果如下:

但是深度使用之后发现,它对于问题的要求非常苛刻,有时候稍微偏一点它搜不到就不会回答。同时他也不能指定知识库作为答案源,回答的质量并不算很高(明显弱于 GPT-4,上下文逻辑性不强),大部分时候不如自己直接搜索,对于回答的结果只能是一个参考性的价值。只能说目前的成熟度还是不够。

工作

接着转化心情,谈一谈工作吧,也是今年「逃离彷徨」主题的由来。

年初 2-3 月时在对未来的工作进行规划时产生了迷茫,之后 5-6 月阳了之后对这种迷茫集中爆发了。虽然我富满激情、虽然我能者多劳、虽然我绩效优异、虽然我履历光鲜,但是这两年我始终无法直面自己的内心——目前的工作内容真的是我喜欢做的事情吗?我只是为了这份不错的薪资和工作中领导、同事对我的肯定,我才一直一直尽全力把工作做到最好。

自 Flutter 项目结束之后,工作上已经没有新的项目能够让我燃起激情了。虽然负责过不少重点项目,但回顾过去两年做的事情,其实都没有给自己带来预期之内的进步。琐碎、简单、单调可以称之为这两年工作的主旋律。

最终在深思之后,我提出了离职。在给领导的信(也叫辞呈吧)中,我这么写道——

「现在我要攻克这个阶段的迷宫,关键在于我要克服自己的心态,但是即便我通关了我所处的迷宫,迷宫劣化的问题依然存在,如果我无视问题的存在,只是调整自己心态进入到下一关,那我只会变得越来越麻木——遇到项目机器人似地高效处理,那我便不再是自己了,我的性格特点将会被磨灭,我所喜欢的、所追求的、所热爱的梦想,也总有一天会在无休止的迷宫闯关中消失殆尽。想到卡明斯说过的一句话:“这个世界日以继夜、竭尽全力让你成为其他人,如果你想做你自己,就意味着要打一场最艰难的仗。“我只想保持自己,这是我的底线。

为了不让自己迷失,18 年开始我每年都会写年终总结,我知道紧握初心不迷失的关键在于要不断自省、反思,之后再从中展望未来。您应该也发现了,我更适合研究型、科研型的工作,因此 Flutter 我可以做的很好,因此哲学论文我也可以写的很棒,再之前也一直如此。比如大三时候可以独自一人住在实验室三天,搞一个机器人项目,可有意思了。回想起来,貌似很久很久没有体会到技术带给我的激情了。除此之外,因为人生经历的原因,我也想追求丰富的人生体验,不会为自己不喜欢的项目去投入 100% 的精力。」

我未曾想到的是,领导竟同意了我离职——他说如果其他人的话他会尝试去挽留,但若是我的话,他能够理解并且给予支持。无论今后如何,他都会支持我。

其实那天早上和他的对话让我解开了这些年的心结,我一直不舍得走更多的原因是看重领导的栽培以及和同事的关系,殊不知它们已成为系紧我的枷锁。但

我所看重的一切,它们并不是枷锁,而是支持我前进的力量。

最终今年并没有离职,因为被上面的领导留了三次,最后决定转组调整项目再试一下。

调整之后得到的成长还是蛮多的——比如业务感,懂得 ABT 实验数据,知道盘子画像对数据的影响,有意识地去通过尽可能多的技术手段去提升某些业务指标,也会通过各种维度去挖掘数据背后的归因。

除此之外还有一些技术上的成长——深入学习了 Cocos 渲染,并写了 C++ 库在项目的游戏引擎上集成了 GFX 模块。还通过这个项目接触了 Android 开发,实现了资源包不同的加载模式,并从零完善并落地了 iOS 的游戏引擎,有机会对 iOS 开发、C++ 开发进行项目实战。

自此之后,基本什么都敲——JS、iOS、Android、C++,一个人调试游戏引擎的全链路,96G 内存的 iMac 也逐渐带不动了。

PS. 某周 WakaTime 的编程统计:

我不能说这是机遇,我只能很庆幸,一路上遇到的都是贵人——他们给予我理解、肯定与支持,让我不再彷徨。

在这过程中并没有丢失掉这些年一直以来的初心,我一直愿景着通过手上的技术给社会带来福祉,虽然目前能做的不多,但申请加入了 W3C 无障碍工作组,也算是迈出了第一步吧。

今年公司还给做了一套 IDP 职业测评,我的数据如下:

抱负能量是职业发展的内核,我的成功愿望居然高达 9.8,思维和执行上也有相当不错的表现。 但与之相反地,人际互动就很低分(我果然还是很适合埋头做研究性质的工作)。

写作

今年一共写了 7 篇月刊,年中的时候因为工作原因停更了好几期:

此外,还在少数派上发表了一篇旧文,意料之外的得到很多感谢的评论:

看着这些善意的评论心里暖暖的,以前觉得写作只是记录自己的想法,不想未来的自己丢失现在的感受,而未曾想到其实自己的想法也可以影响到他人。

之前听闻“婴儿最快乐的时刻就是发现他自己可以对周围的世界产生影响”。在写作这种创造性的活动中,或许我也是在追求某种“存在感”,这种存在感可能就是我对世界的一点点影响和改变。

某天在知乎上收到了一个私信,真切地感受到写作给他人带来的帮助:

之后便在月刊中鼓励读者与我写信交流,今年陆陆续续收到一些读者的来信,我也积极认真地逐一回信了。2024 年初预计启动一个读者来信计划,可以期待一波~

技术文章就写了两篇:

  1. 《Atum 自研游戏引擎高性能探索之路》:暂未脱敏,就没发到外网了。
  2. 大厂自研跨端框架技术揭秘

较往年而言技术文章写的少了,一方面是因为时间分配问题,另一方面是很多内容原本想写、但觉得写完之后没法脱敏所幸就不写了——但不管怎样,技术文章这块明年需要补足。

大家已经发现了,今年的周刊是在新的博客上发布的。因为前段时间服务器和博客被恶意攻击产生了损失,这几天将迁移了博客到新平台 blog.ursb.me,并对相关资源做了加固。新博客整合了之前的小屋和月刊,小屋和月刊两个网站直接关停,目前对小屋和月刊的核心页面做了 302 处理,而评论的迁移过于麻烦就直接丢弃了。之后请直接关注新的平台,订阅博客之后也会有邮件推送新文章。

书影音

今年看的剧不少,以下排名按个人评分排序。

剧集

神作档:

  • 《重启人生》:★★★★★,心底里的六个星,每次重温都能发现温暖的小细节。
  • 《漫长的季节》:★★★★★,披着悬疑片壳的文学佳作,兼具优异的叙事模式和情感内核。

好评档:

  • 狂飙》:★★★★★,后悔现在才看。但是看的是删减版,只能下调一档了。
  • 《黑暗荣耀 1 & 2》:★★★★★
  • 《洛基 2》:★★★★★,所以为什么时间宝石的绿色的。
  • 《隐秘的角落》:★★★★★,重温的佳作。
  • 《最后生还者 1》:★★★★★,本身剧本好。
  • 死期将至》:★★★★★,意料之外的好片。
  • 三大队》:★★★★★
  • 《新闻女王》:★★★★★,中规中矩的 TVB 剧集。
  • 《繁城之下》:★★★★★
  • 《三体》:★★★★★
  • 《凪的新生活》:★★★★☆

值得一看档:

  • 《白夜追凶》:★★★★☆
  • 《都挺好》:★★★★☆
  • 《恶鬼》:★★★★☆
  • 莲花楼》:★★★★☆
  • 别班》:★★★★☆
  • 《好事成双》:★★★★☆

可看可不看档:

  • 《故乡,别来无恙》:★★★★☆
  • 《宁安如梦》:★★★☆☆

电影

神作档:

  • 《蜘蛛侠:纵横宇宙》:★★★★★
  • 《情书》:★★★★★

好评档:

  • 《奥本海默》:★★★★★,4.5 星。本来很期待的诺兰新片,但如果没有提前看过解析,根本不知道通过黑白来转化视角的表达方式,还是太隐晦了,会导致有些云里雾里。
  • 《消失的她》:★★★★★
  • 《满江红》:★★★★★
  • 《银河护卫队 3》:★★★★★,对于漫威而言 5 星只是及格分。但是这几年漫威在影片荧幕上没有及格的影片,这个银护 3 就显得很难能可贵了。
  • 《网络谜踪 2》:★★★★★
  • 《年会不能停》:★★★★★

值得一看档:

  • 《保你平安》:★★★★☆
  • 《孤注一掷》:★★★★☆
  • 《坚如磐石》:★★★★☆,老戏骨还是老戏骨。
  • 《饥饿站台》:★★★★☆

可看可不看档:

  • 《八角笼中》:★★★★☆
  • 《封神 1》:★★★★☆
  • 《分手的决心》:★★★★☆
  • 《名侦探柯南:黑铁的鱼影》:★★★☆☆,心疼琴酒。
  • 《末日崩塌》:★★★☆☆
  • 《人生路不熟》:★★★☆☆
  • 《长空之王》:★★★☆☆
  • 《流浪地球 2》:★★★☆☆

烂片档:

  • 《蚁人与黄蜂女》:★★★☆☆
  • 《天龙八部之乔峰传》:★★★☆☆
  • 《雷神 4》:★★☆☆☆,讲了个啥?
  • 《前人4:英年早婚》:★★☆☆☆
  • 《Nope》:★★☆☆☆
  • 《变形金刚:超能勇士的崛起》:★★☆☆☆,特效不错
  • 《鹦鹉杀》:★☆☆☆☆,打一星是因为最差只能打一星

动漫

  • 《咒术回战 1 & 2》:★★★★★,刚粉上五条悟漫画里就被腰斩了…
  • 《三体(动画版)》:★☆☆☆☆,看一半弃了,看下去的动力居然是评论区和弹幕的调侃

阅读

  • 《存在主义心理治疗》:★★★★★
  • 《人类灭绝》:★★★★☆
  • 《消失的 13 级阶梯》:★★★★☆
  • 《悉达多》:★★★★★
  • 《<资本论>的读法》:★★★★☆
  • 《少年巴比伦》:★★★★☆

游戏

今年没有怎么玩游戏,《原神》须弥主线硬着头皮刷完了、枫丹没有开;《王国之泪》只打了一个 Boss,《博德之门》只出了新手村,《潜水员戴夫》也就下海了几次。

新年目标

又到了新年目标环节了,依旧不定具体的目标,而是希望自己始终保持危机感。

罗曼罗兰说过一句话:“大部分人在二三十岁上就死去了,因为过了这个年龄,他们只是自己的影子,此后的余生则是在模仿自己中度过,日复一日,更机械,更装腔作势地重复他们在有生之年的所作所为,所思所想,所爱所恨。”

为了紧握初心、保持进步,2024 送自己三句话:

  1. “铅刀有干将之志, 萤烛希日月之光。”
  2. “生也有涯,知也无涯,向死而生,求知不怠。”
  3. 每天默念:“我尊重一切发生,我追求内心渴望,我相信自己每一天都变得更好。”

月刊(第24期):十年编程之路

本篇是对二〇二三年十月生活的记录与思考。

十年编程之路

时光荏苒,回首望去,自系统接触编程已近十年,这篇月刊就写写这十年的编程之路吧。

深得周围小朋友的羡慕,家里小时候是开网吧的,因此我对电脑游戏向来比较熟悉,但未曾设想过以后会走向编程的道路。2007 年的时候,初中组织了一波 Pascal 的奥赛班我也半途而废了, 2013 年高考之后报了教育技术学专业,这是一个在教育学院里和计算机稍稍沾些边的理科专业。如果按照正常的轨迹发展,想着大概毕业以后凭着专业的教师资格回老家的中小学当一名老师吧。

但一切在 2014 年的年初发生了改变。

2014:初识

年初那会儿那段逼近死亡的经历给我带来了巨大的变化,后面的每一天都有向死而生的感觉。如同《存在主义心理治疗》中对死亡焦虑的剖析:“死亡的觉察使人们脱离对琐事的关心,为生命提供深入、强烈而完全不同的观点。”也是自那之后,我的人生观发生了重大的转变——人生目标定为想要为世界留下些什么。我不愿按照之前的既定之路走完一生,于是重新安排生活的优先级,努力找寻未来人生的方向,恰好那时有一门 VB 的课程我很感兴趣,也是 VB 让我正式敲开了编程的大门。

记得接触 VB 那会儿自己开发文字 RPG,每天晚上搞到一两点仍意犹未尽。

姓名大乐斗最终迭代了 6 个版本:

那段时间的每个深夜里,满怀激动地开发着游戏,这种心情哪怕是现在回忆起来也仍未有任何衰减。现在想来,那恐怕就是最纯粹的热爱吧。

因为课程表现优异,被老师拉进了项目组,之后自学了 Java Swing 和数据库,做了一些游戏化的课件和管理平台。

在 VB 和 Java Swing 时期我开发的 GUI 小玩具们都是直连数据库的(不敢相信),为了弥补上中间的桥梁,也是因为向往做一些联网应用,于是又学了 ASP.Net 和 JSP,那个时候不管开发什么 API 都是直接 Struct + Hibernate + Spring 一套撸上去。那个时候感觉自己无所不能,貌似自己凭借着 GUI、JSP、Access 就已天下尽在掌握之中。

之后和每个技术人一样,我购买了自己的域名并备了案(ursb.me ,一直使用到了现在),并魔改了 WordPress 的主题把博客部署在虚拟主机中。(所谓虚拟主机也是古早时期的产物,就是服务器上给你一块目录随便操作。)

那个时候博客大概长这样(古早的 3G 网络和 iPhone 5):

大二的时候,我们学校开放了一些第二专业,我便申请辅修了计算机,每天白天上本专业的课程,晚上去上二专课,九点多下课回宿舍捣鼓自己的东西。虽然有点辛苦,但是回忆起来异常充实,而且稀里糊涂地总是第一

2015:Web 前端

一五年的时候接触了 HTML、CSS 和 JS,也就是现在所谓的 Web 前端。刚接触 H5 开发的时候还在用 DreamWeaver…之后了解到了 Sublime,便把 DreamWeaver 卸载了。

学 JS 期间用 Canvas 写了个微信聊天界面模拟器,项目也迅速上了 100 Star。

在那之后写了一本 Canvas 的教程,也收获了许多 Star:

打印出来之后给班上同学授课也是成就感满满:

这一年年初,和专业的同学组队去参加了人生中的第一次 Hackathon,团队决定写一款叫 AskNow 的 Android App,由于我不会 Android,所以被分配去画 UI 了。经过这个项目学习了 Sketch,也了解了移动端的一些基本组件,之后又对移动端开发感兴趣学了段时间 Android。

由于手上没有 Android 设备,总是在模拟器开发很没意思。恰那时 Swift 刚出没一年,立马开始学起了 iOS 开发,那个时候简直是 iOS 开发的黄金时代,而自己的学习欲望也特别强,感觉什么东西能有兴趣,什么东西都能学下去。

那一年,苹果发布了史上第一款 Apple Watch,当时看得满眼冒星星,马上去 Apple Store 买了一个,并付费了 Apple Developer 资格证书,开始开发 Apple Watch 应用:

很庆幸自己生在这个时代,时不时有新技术涌现出来,也毫无顾虑地可以学习一切想学的技术。

2016:无限可能性

学完 Swift 之后便跃跃欲试,做了一个 App 并上架了,成就感满满:

在那之后又做了一款 Apple Watch 的宠物养成+英语学习的 App —— FeedMe:

这一年还学了 R 语言还有 Python,做了一些爬虫和数据分析的项目,迷上了 Kaggle。但可惜的是后来没有往这个方向发展,当时也觉得学了以后对做软件可能没有帮助。(但神奇的是,今年年初在对一个外网现象做技术数据归因分析的时候用到了!当时觉得好不可思议!)

这一年参加许多竞赛,其中有一个项目是电子信息技术专业的,做一个搬运机器人。刚接触项目的时候对单片机没有任何了解,为了恶补知识和赶项目进度,项目期间还搬进了实验室住。那个时候实验中心的项目组给我分配了一个独立的实验室,劳累着、幸福着。

做完这个项目之后,发现自己某种意义上成为了全栈工程师——UI 设计、Web、后台、客户端、硬件。我对这一切都感兴趣。

那段时间跨端技术开始初露头角,Ionic 和 Cordova 也是立马学了然后撸了几个 App 出来参加比赛,感觉自己的精力好像是无限的…

而这些比赛基本上都获奖了,也算是收获满满了~

PS. 这一年的空余时间依旧在折腾我的博客:

(明明是最简单的 Hexo,被折腾的臃肿不堪)

2017:前端大杂烩

前文说到博客,这一年我最后一次修改博客,力求简洁,于是改成了现在的样子(me.ursb.me)。自此之后再也没有动过博客的样式,博客本应该以内容质量为本。

2017 年过年期间,又一次生病住院了。住院期间我记得微信发布了小程序,然后对着官方文档敲了个小程序出来:

(现在看来简直不敢相信自己能左手敲代码。)

那一年学习了 VUE,做了个社区:

那一年的本科毕业论文写了个推荐系统。

那一年入手了个咕咕机和树莓派,做了个玩具:

那一年参加了很多比赛,在互联网上组建了零熊团建,用 React Native + Express 做了一图、双生、四时等众多上架了 App Store 的 App:

那一年接了个健身房的外包,做了一整套卖课、约课、排课、打卡、监测上课运动状态的系统,赚了不少生活费:

这一年的年终总结:再见了,我的大学

2018:实习

这一年上半年主要在重构 双生日记 2.0,最终凭借它拿到了小程序大赛的 Top2。

3 月份开始秋招实习面试,凭借着这份校招简历和不错的八股文准备,如愿进入了鹅厂,岗位是 Web 前端(当时觉得前端比较好准备)。自此之后,一直一直在这个团队做到了现在。

那个时候的工卡照(真年轻啊…)。

这一年的年终总结:2018,沉淀初心

2019:WWDC 与工作

这年赶上了两年学硕的末班车,顺利硕士毕业。毕业前夕收到了 WWDC Scholarship 送的门票和住宿,加上浙大慷慨给我中大学子包机票,免费去美国玩了几天。

这趟旅途上认识了很多厉害的同学,也知道了前途永无止境,需要不断向前。

回来之后正式入职,前几个月基建有点落伍,还在写 JQuery,年末团队推动了 React。这年除了 React 业务活动页面,还做了个小程序,也用 Hippy 做了一个独立的 App。

这一年的年终总结:2019,走走停停

2020:Flutter

这一年跨端异常火爆。我这年的主旋律有幸是 Flutter,在团队内主导 Flutter 混合开发框架的建设,也做了一些 iOS 侧的研发工作。基于项目做了一些 BU 和司外的分享,还拿了两个五星,这也是毕业之后第一次拿五星绩效,对个人的激励还是非常大的。

这个项目让我重燃了对原生开发的兴趣,也激发了对底层技术的钻研精神。自此之后,技术宽度收窄,开始深挖。

这一年的年终总结:2020,追逐星火

2021:中台与元宇宙

这一年中台概念兴起,我们也做中台;年底元宇宙兴起,我们又开始做 3D 渲染。

这一年业务繁忙,被推着往前走,鲜有沉淀。

这一年,从 T6 升到了 T9。

这一年的年终总结:2021,自渡向前

2022:工程化

这一年年初升到了 T10。

这一年专注 3D 渲染和 Web 工程化,但是成长不多,如果说成长率=学到的知识/时间,反思一下这两年的成长率是很低的。于是寻求突破,业余时间搞搞开源项目、看浏览器内核代码、写文章,虽然鲜有练手,但是知识储备上丰富了不少。

这一年的年终总结:2022,平安喜乐

2023:iOS 与游戏渲染

这一年主要在做游戏渲染这块,技术上主要做 iOS 容器和 C++ 跨端,但同时也要写一些 ts 和 debug Android 问题。

十年,大概就是这些啦。

最后分享一下学习编程的技巧,其实从前文也能看出来,最核心的是热爱,其次是要以项目为中心,学了什么就想着去做个东西出来用,但还有一些文章里没有谈到——需要沉淀。

  • 学习过程:看官网文档即可。
  • 应用过程:以项目为目标是为了加深对前一个环节的理解,同时做出东西出来也很有成就感。
  • 沉淀过程:开发记录总结,写博客分享。

学习过程中除了做笔记:

对问题的深入研究也需要把核心点画出来:

画出来可以贴图记录现场,同时思路是白板般的四处扩散,这个比普通的笔记记录好。

最后知识的输出就在于勤写技术博客。

希望自己还能在这条热爱的道路上再走出下一个不一样的十年。

🌺 生活点滴

以下是十月里一些瞬间的记录。

🚗 国庆假期自驾游

自驾路线:深圳-顺德-广州-清远-英德-从化-广州-深圳

感想:清远漂流真好玩!

🧟♀️ VR 打丧尸

国庆的末尾去广州见了高中同学,四个人一起玩了 VR 打丧尸,贼有意思。

还抓了一堆娃娃:

🎧 AirPods Pro

之前的耳机被洗衣机洗了,有时候播音乐会有杂音,但也勉强继续用了两年,这次换了一个新出的 USB-C 口的 AirPods:

🚗 保时捷

和一个土豪朋友去试驾了 718 boxster,感觉我更喜欢我的 Mini 了。

🏄🏻 团建

部门团建,去了惠州的一个岛上(第一次坐帆船)。

🏃🏻♀️ 体重

从小到大,基本都在 55 kg 左右,从来没超过 60。最近一直在上涨,感觉有望突破 65。

🐱 手套

手套最近特别喜欢蹂躏这只狗,每天把这狗藏起来晚上,手套总能把它拖出来打一番:

🎬 书影音

以下是这个月的书影音:

  • 在读:心理学 |《存在主义心理治疗》| ★★★★★
  • 在看:美剧 |《洛基2》| ★★★★☆
  • 在看:日漫 |《咒术回战2》| ★★★★★
  • 在看:日漫 | 《间谍过家家 2》| ★★★★★
  • 在看:纪录片 |《地球脉动3》| ★★★★★
  • 重温:剧集 | 《沉默的真相》| ★★★★★
  • 看过:剧集 | 《繁城之下》| ★★★★☆
  • 看过:电影 | 《请叫我英雄》| ★★★★☆
  • 看过:电影 | 《坠落的审判》| ★★★★☆
  • 看过:电影 | 《坚如磐石》| ★★★★☆

月刊(第23期):多任务中的时间管理

本篇是对二〇二三年九月生活的记录与思考。

多任务中的时间管理

这期应读者期望,聊一聊时间管理。

对时间的管理有两个基本方面:

  1. 做事前,对时间的分配
  2. 做事时,对时间的使用

分配时间:区分优先级

每个人的精力是有限的,但事儿却接近是无限的——工作会有一堆待安排的事儿,生活中也会有许多琐事需要去做,那么如何合理地安排好自己有限的时间呢?

简单分享下自己的方法:日历+提醒事项。其中:

  • 日历:用来记录 已固定时间的、不得不去做事项。即,我的这段时间固定被这个时间段的 Event 消费掉,需要安排进日历里。比如会议、球局、饭局等等。(下图中原点打头的就是 Event)
  • 提醒事项:用来收集琐事和待办,它们是一个一个 Things,可以相对地根据优先级的不同被灵活安排。如工作的待办、生活中的琐事等等。(下图√打头的就是 Things,已经完成的就可以勾上)

软件这里我的用的是 Fantastical,使用了 iCloud 的 Apple 日历和 Todoist。之所以使用 Todoist 是看中了它的开放性和集成能力,可以在 Obsidian、Raycast 等各种三方软件中被集成进去。TODO 类的软件有很多,用自己喜欢的、顺手的就好。

当然,没有特别需求的话,用 Apple 的日历和 Apple 提醒事项也就 OK 了。

工具介绍好了,那么具体该如何分配时间呢?这里可以灵活安排的时间主要是 Things,这里有几个小技巧可以分享一下:

  1. 根据优先级分配:高优且紧急的必须立刻做,低优或者生活琐事我会安排在早上起床或者晚上睡前统一处理。(至于如何区分优先级,考虑的因素有很多,此处不扩展说了)
  2. 根据自己的专注时间段分配:发掘自己的专注时间段,是晚上效率高还是下午效率高,把一整段时间空出来,然后把相对困难或重要的事情放在这个时间段去处理。

当然,事情可能会在工作中随时安排进来,来了事之后,如果是非紧急的事情就不要停下手上的工作,而是把它们先放进 Things 的收集箱(Inbox)里,待切换工作上下文的间歇再去回顾、安排、调整 Inbox 和已安排实现,看看优先级和时间段是否需要变化。

消费时间:保持专注、全力以赴

时间对于每个人而言都是公平的,每个人的一天都是 24 小时,那保持高效的时间利用率就能把更多的精力节省出来做更多的事

前两年的年终总结中我说过自己不擅多线程工作,后来做了一番调整,核心要点在于——一天中保持尽可能长的专注时间、尽可能少的切换工作上下文。

前两周的《纽约时报》有一篇文章写到:

多任务处理在很大程度上是一个神话。我们可以一次只专注于一件事。“这就像我们头脑中有一块内部白板,”马克说。“如果我在做一项任务,我的大脑白板上就有我需要的所有信息。然后我转而使用电子邮件。我必须在脑海中抹去那块白板,写下写电子邮件所需的所有信息。就像在真正的白板上一样,我们的脑海中可能会有残留物。我们可能还在想着三项任务之前的一些事情。

多线程工作会占用我们的认知资源,也会干扰我们的工作记忆,更会摧毁我们进入心流的可能性。因此工作中需要尽可能保持专注,摒除非工作的影响因素和尽量避免被打断。回过头来看,人类在文化领域的成就,包括哲学思想,都归功于人类深刻、专一的注意力。而随着功绩社会的兴起,这种深度注意力却日益边缘化。

我们知道,经济学是研究社会中稀缺资源如何分配的问题,而如果我们将这个概念中的「社会资源」置换成「个人资源」后,稀缺的概念和原则依然适用,一个人最稀缺的资源可以概括提炼为「时间、精力、注意力、专注力」,这些资源使用在一个地方的时候,就不能使用在另一个地方了。

《稀缺》一书中谈到一个观点:

人的大脑就像一条道路,有自己的「带宽」。当这条道路被某些东西占满时,人就会耗尽认知资源,陷入「满负荷」的状态,无暇去思考其它的东西。

综上,高效的秘诀在于「专注」。

我一直在用「Rize」,它不单纯是一个简单的 Time Tracer 工具:

而是一个培养 Focus 的工具。同一软件或是保持在同一工作上下文中保持一段时间,会进入 Focus 状态(类似心流),这个时间会统计 Focus 时间。而如果你突然切出上下文,如打开微信或者用浏览器浏览了这个上下文无关的网页,Rize 会认为此刻你分心了,从而弹出保持 Focus 提示并退出此轮 Focus 计时。

整个软件设计地异常简单,整个流程不需要去配置任何东西,结合 AI 进行全自动的监测,只有在娱乐或者是打断 Focus 的时候才会感知到它的存在。(PS: 这不是广告,但如果想体验可以用 https://rize.io?code=291287 链接来注册…)

当然,再完美的软件也无法替代时机的行动,世界上多的是一张白纸也能把自己的生活安排得明明白白的时间管理大师,研究多少方法论,也不如老老实实做事实在。决定时间价值的,不是你拥有什么,而是你产出了什么。

对于我们的时间,重要的不是「当下的回报」,而是它的成长性和可能性。培养练习自己对于个人能力的认知、对于时间的感觉,从而合理调整安排自己的精力资源和时间资源,在有限的时间和有限的资源下产出更多的成果。

对于有限的时间,也无需过于焦虑,无论面对多少事情,全力以赴便是幸福。当我们准备做一件事时,无论这件事情大小,无论这件事在社会评价体系中的重要性如何,郑重地将自己全部投入进去,不计得失地投入自己的才华、注意力和时间,就会从心底感受到一种愉悦和对自己的肯定。

或许一切如同上期月刊中分享的《时之形》中说的那样:

“与其挣扎求索,不如素然以对,或许那样才能画出相对美丽的生命轨迹。”

🌺 生活点滴

以下是一些瞬间的记录。

🔨:啥都敲工程师

转了岗之后开始啥都敲,有点终端全栈了。日常对渲染的研究日渐深入,经常一个小问题卡两三天,但是解决之后成就感满满。

那之后,心情和心态一直在变好。

📱 iPhone 15 Pro

换了新手机,感觉没啥变化=。=

🐬 错峰度假

国庆前一周请了个周五的假,凑三天小长假去长隆玩了圈~

🌇 夕阳

某日晚饭后回到工位,惊鸿一瞥的夕阳。

🎬 书影音

以下是近两个多月的书影音:

  • 在读:哲学 |《<资本论>的读法》| ★★★★★
  • 读完:小说 |《人类灭绝》| ★★★★☆
  • 看完:电影 |《奥本海默》| ★★★★☆
  • 看完:电影 |《孤注一掷》| ★★★★☆
  • 看完:电影 |《封神:朝歌风云》| ★★★★☆
  • 看完:电影 |《末日崩塌》| ★★★☆☆
  • 看完:电影 |《变形金刚:超能勇士的崛起》| ★★☆☆☆
  • 在看:日漫 |《咒术回战2》| ★★★★★
  • 看完:日漫 |《咒术回战》| ★★★★★
  • 看完:日漫 |《钢之炼金术师FA》| ★★★★★
  • 在看:韩剧 |《超能异族》| ★★★★☆
  • 看完:日剧 |《VIVANT》| ★★★☆☆
  • 看完:古装 |《莲花楼》| ★★★☆☆
  • 看完:现代 |《装腔启示录》| ★★★★☆
  • 看完:纪录片 |《地球脉动2》| ★★★★★
  • 在玩:Switch |《塞尔达传说:王国之泪》| ★★★★★
  • 在玩:Steam | 《地平线 5》| ★★★★★

刚看动漫粉上五条悟,结果隔天在漫画里就人设崩塌了… By the way,《钢炼》不愧为经典神作。

最后一个小广告,我在 TG 上注册了 Channel,利用 Bot 和 WebHook 实现了个行为收集站,每当我发了月刊、博客或者收藏了一些网页、文章,Bot 都会在 Channel 里进行自动通知,有兴趣的朋友可以订阅:https://t.me/airingchannel

月刊(第22期):当下的快乐

本篇是对二〇二三年五月至八月生活的记录与思考。

最近周末比较忙导致断更了,感谢催更同学的关注:

💐 当下的快乐

本文标题来源于莪默·伽亚谟《鲁拜集》——“享受当下的快乐,因为这一刻正是你的人生。

PS. 我一直把月刊中的文章定位为“自我对话”,只有对话结果满意的文章我才会拿出来修整一番重新在博客上发布。因此如果这篇文章最终行文紊乱,请多包涵,我也没有把握仅靠一篇文章就把这个议题讲清楚。此外,如果读者发觉文章中可能含有说教意味,请无视;如果有,那么这部分应该是我对自己的说教。

求而不得的意义感

是否已经很久没有过快乐的感觉了?

在如今的环境下生存,人们对意义的感受会通常和焦虑联系在一起。追求生活的意义,找寻存在的价值,最后得出的结论无外乎“好好努力”——从而不断压榨自己的精力和时间去追求产出、追求实用、追求充实,将自己的生命意义定义在「努力焦虑」之下。

仿若人生在命运之中逆水行舟,不进则退。在这种压力之下,内心难以平静,也很难完全放下心来去体会快乐是什么;而另一部分人们则选择完全躺平,放弃去追逐意义感以摆脱「努力焦虑」;还有一部分人则在内卷和躺平之间不断做仰卧起坐。(至于如何在生活和工作之间保持平衡,这又是一大话题。这个问题的完美解和每个人的生活节奏有关系,暂且不展开说了。)

从前的我会觉得在学习和工作之余的快乐瞬间总是伴随着的是莫名其妙的负罪感,当然这不是刻意为之,而是小时候各方面歌颂苦难的教导——“今天吃的苦会在未来收获甜,而今天如果感受到甜,那不久的将来一定会吃到苦”。

后来想了想,人只有在背离自己的内心时才会害怕,从而诞生出焦虑和负罪感。

我们害怕的是无法坦然面对自己;

我们害怕的是碌碌无为终此一生;

我们害怕的是对意义感的求而不得。

有位领导对我说:“如果自身的内心是充实的,那么即便一生卖烧饼,也是有价值的一生。”

思索一番,这番话并不是鼓励出世,也不是让我对环境和命运妥协。如果拼尽全力,发现自己的命运真的是无法抗争,那就认清它——“事实就是这么悲哀,不是所有人都能功成名就,我们中有些人注定要在日常的点滴中去寻找生命的意义。”

即便如此,也不要忘记“甘于平凡的勇气”。

如果大环境无法改变,如果命运无法抗争,那么人类最后自由就只剩下自己对生活的态度了。正所谓“所有东西都能被抢走,除了一件事,人类最后的自由——去选择在当前环境下自己的态度,选择自己的方式。”

这又让我想到了之前在《心灵奇旅》幕后采访中看到的一句话:“成就不在于外在定义,全看是否按自己的意愿在活。

那么这个问题的解法在于“当下的快乐”,具体有二:

  1. 珍惜每个当下瞬间
  2. 知觉生活中的快乐

珍惜每个当下瞬间

人是一个一个的、在不同瞬间的“存在”,人之所以成为人,乃因为他存在。换言之,如阿德勒所言:“人生是一连串的刹那,最重要的是此时此刻。

要知道当下的瞬间就只有当下,没有过去、也没有未来——对过去的后悔,以及对未来的担忧,正是影响我们生活和工作的最大敌人,也是破坏我们幸福感的元凶。

我们常会遗憾未曾选择的路,会常常去想如果当时我不这样去选,而走另一条路会不会当下的结果会更好一些?

然而一切其实都不可知,《重启人生》中就戏谑地表达了所谓“最优选择”的结局,即便重启人生不断尝试走了另一些路,回头来看,或许还不是最初的选择。《寻找天堂》中的 Sopia 说:“人的一生如此短暂,我们不可能完成每一件想做的事。无论我们做了什么,总有其他东西值得一试,总有另一条路值得一走。所以到最后,我们能做的,也是必须做的,是满足于自己所选择的路。”

要知道,现实没有如果,我们有的只有一个一个当下的瞬间,在当下的瞬间做出的选择无论结果怎样,它就是最好的。

珍惜当下,珍惜此时此刻,珍惜身边的一切。我衷心希望所有人都能学会感受这些瞬间,而不要让它们白白地溜走。

知觉生活中的快乐

首先要知道,是快乐构成了我们生命中的幸福感,而不是其他。

在压力较大的环境下,我更喜欢一些轻量化的情绪出口,或许越简单、单纯、质朴的东西,越能净化心灵。我特别喜欢看“每日豆瓣”中推送的文章,千奇百怪的豆瓣小组中总会有那么些人喜欢分享身边的开心事儿:

  1. 很多时候,似乎下意识觉得,快乐不是一件像工作、学习那样的正经事。所以,每天会安排工作学习,却不怎么安排快乐的事。 但有时候还是会随机做点快乐的事,比如学唱喜欢的歌、练习吉他、看电影、写日记、查菜谱做自己喜欢的菜、做奶茶等饮料、趴窗台发呆(看云,看楼下的老人小孩)、看跟最近问题有关的书等等。——罗恬恬
  2. 喜欢看天,看云卷云舒,看晚霞变色,很放松,最近大理的天空都是七彩祥云,今天等了一个傍晚真的看到了一点点就很开心。还喜欢让阳光透过窗帘洒在书上一点点看书,光影斑驳但是却很幸福。可以收拾房间分门别类,清理不需要的东西,会很治愈。——浪漫小孩

能够知觉生活中的快乐是一种值得羡慕的能力,然而这种知觉力本身就很难得。在 《谈谈存在的价值与人生体验》 一文中我曾写过“敏感决定了对世界的感知边界(轮廓)”。《德米安》也不断强调知觉的重要性——“只要他对此没有知觉,他就只是一棵树,一块石子,最多称得上是一个动物。然而,当这种知觉开始闪出第一道微光时,他便成了一个人。在你的眼中,或许并非所有走在大街上的两腿动物都能称得上是人,虽然他们也能直立行走,生儿育女。你心里明白,其中大多数人仍是鱼羊虫豸之辈,多少人生如蝼蛄!当然,每个人其实都有变成人的无数可能,但只有他了解到这些可能性的存在,甚至有意识地去认识这些可能性时,他才真正拥有它们。”

那么如何培养这种感知快乐的知觉力呢?我总结了一下,有以下四个途径:

  1. 慢下来
  2. 培养专注
  3. 拉长视野
  4. 永葆善良

一、慢下来

把注意力从瞬息万变的外界抓回来,不要被外界的信息爆炸所吸引。需要更多地去关注自己的内心,关注每一天的感受。写日记就是一个不错的办法——“记录幸福快乐的体验,重温那种良好感觉能让人更快乐。写下坏事,发泄式地写,可以宣泄痛苦情绪,帮助恢复心理健康。”在《WJ.19: 日记的意义》里写过日记的好处,这里就不展开了。

我们的生活只有在慢下来,去关注它的时候,它才切实存在。同时我们追求高效,是为了可以慢下来去体验生活,而不是失去生活。

没有任何事比好好睡觉、开心、健康更重要。

二、培养专注

《如何应对心里的难》说到:”当我们真的调用全部的身心资源,带着「究竟发生了什么」的好奇感去感知一个苹果、一棵树、一个人、一件事,或者感知我们自己的肌肉、温度、心跳、呼吸等体验变化时,我们会瞬间进入“忘我”状态,也会因此体验到宁静、祥和,以及无意识的喜悦。 ”

当我们全身心地投入在手头所做的事情上,沉浸在它所带给自身的反馈和感受之中,体验着当下的每一瞬间——这种心流状态中的的心绪将一片宁静,不会有任何烦恼和忧虑。这是一种特别舒服的状态,会感到跟这个世界融为一体,时间仿佛停止了走动,心灵被满足感充盈,真切地感受到自己的存在。

三、拉长视野

我们的生命是属于自己的,我们所度过的每一分、每一秒,都是自己切切实实的人生。如果人生不能以喜欢的方式度过,那岂不是在浪费生命,这一切又有什么意义呢?

拉长视野来看,人生很广阔,或许大多数人是平凡的,但是没有人会是平庸的。所有人都拥有同一个起源和母亲,我们来自同一个深渊,然而人人都在奔向自己的目的地,试图跃出深渊。我们可以彼此理解,然而能解读自己的人只有自己。

每个人都与众不同,每个人的生命都是通向自我的征途,是对一条道路的尝试,是一条小径的悄然召唤。人们从来都无法以绝对的自我之相存在着,每一个人都在努力地去找寻、去成为绝对自我,这个过程中有人迟钝,有人更洞明,但无一不是自己的方式。选择自己喜欢的方式去度过一生,多去体验人生,那么当下的每个瞬间都会是快乐的。

没有那么多大不了的问题,需要如此枕戈待旦,天天紧张得要死。生活中该是有些意料之外的好东西的,会被一些偶然送到我们身边。

人呢,只要看穿了这个世界的矫饰,世界便会属于你。

四、永葆善良

这个点在《谈谈存在的价值与人生体验》 一文中也曾谈到过,不过那个时候是以建立联结的角度去分析的,不再重复赘述。

这里补充想介绍下《悉达多》中的佛陀乔达摩,他熟稔人性的无常、空幻,却依然深爱并倾尽一生去助佑、教导世人。“在这位伟大的导师心中,爱事物胜于爱言辞。他的作为和生命重于他的法义。他的仪态重于言论。他的伟大不在他的法义中、思想中,而在他的生命中。”

认识到了乔达摩的伟大智慧,悉达多说道,”对于我来说,爱乃头等要务。审视世界、解释世界或藐视世界,或许是思想家的事。我唯一的事,是爱这个世界。不藐视世界,不憎恶世界和自己,怀抱爱,惊叹和敬畏地注视一切存在之物和我自己。“

我想,如果可以达到此种人生状态,对世界抱有爱,对万物怀揣善意,那么生命中的每一刻也都将是最纯粹的快乐。

🌺 生活点滴

最近两个月经历了很多事,算是工作以来最坎坷的一段时间了,过程和细节就先省略不说了,曾处在焦虑和不快乐的状态中,还好调整回来了(这也是这期选题《当下的快乐》的原因)。以下是一些瞬间的记录。

🐑:六月一日,阳了

终于还是中招了,首阳非常难受,基本只能躺着不能动,以至于鸽了5月的月刊。阳了的第二天,高烧中发现自己手脚开发发麻,直到最后只能维持在一个奇怪的姿势无法动弹。幸亏 Belle 在,给我抬去了医院。检查之后是由于过度呼吸导致碱中毒,从而引发了手脚搐搦,控制呼吸节奏就自己好了。虽然没啥问题,但是我在过程中还是想了很多——应该把握住每一个当下的体验,好好地去利用宝贵而有限的生命。

🎂:六月三十日,生日

今年生日没有发朋友圈,因为我比较排斥这一天的到来——毕竟 28 岁了,开始感到年龄的焦虑了。

🤒:七月,肠胃炎+扁桃体炎发烧

不知道吃了啥坏东西,双症齐发直接搞到了40度,去医院之后医生开了一堆药(还得按一定的规则食用和储藏),药很管用,吃了两天直接就好了。

🏸:羽毛球

身体好了之后开始恢复打球,周三、周六、周日三选二,基本保持一周两场的状态。

👨🏻💻:工作,尝试转 iOS 开发

工作这块最近变动有点大,这段时间认识了很多优秀的人,也遇到了一些赏识我的人。我能感觉到这些目光中对我抱有的极大的期待,但也很害怕自己的表现会让他人的期望落空。总之,能做的就是自己好好努力——尽我所能,把当下的事情做好。此外,工作内容也从 Web 开始转 iOS 开发,并钻研着我喜欢的技术需求,一直向往着客户端岗位总算得到了机会,希望这一切都在变好吧。

🏠:搬家

依依不舍离开了满分的房子,附房间纪念图一张:

(不过搬到了120 分的新屋子!)

👩🏻🦯:W3C

加入了 W3C 无障碍工作组,在工作组会议上见识到了社区的工作模式,看到了一堆牛逼的外国同行。如果以后能够为无障碍领域贡献出自己的一份力,也算是达成了职业理想吧。

🏆:公司培训

这两个月参加了个公司级的培训项目,通过项目认识了优秀的同组同学,希望未来能维持“革命友谊”。

🍵:校招分享

荣幸参加了公司校招培训的座谈会分享,结识了同年龄段优秀的人。

🎬 书影音

以下是近两个多月的书影音:

  • 读完:哲学 |《德米安》| ★★★★☆
  • 在读:经济 |《经济学原理:微观经济学》| ★★★★☆
  • 看完:剧集 |《恶鬼》| ★★★★☆
  • 看完:剧集 |《D.P:逃兵追缉令》| ★★★★★
  • 看完:剧集 |《漫长的季节》| ★★★★★
  • 看完:剧集 |《隐秘的角落》| ★★★★☆
  • 看完:剧集 |《都挺好》| ★★★☆☆
  • 看完:剧集 |《白夜追凶》| ★★★★☆
  • 看完:电影 |《蜘蛛侠:纵横宇宙》| ★★★★★
  • 看完:电影 |《网络迷踪2》| ★★★★★
  • 看完:电影 |《雷神4:爱与雷霆》| ★★☆☆☆
  • 看完:电影 |《银河护卫队3》| ★★★★★
  • 看完:电影 |《消失的她》| ★★★★☆
  • 看完:电影 |《八角笼中》| ★★★☆☆
  • 看完:电影 |《人生路不熟》| ★★★☆☆
  • 重温:电影 |《禁闭岛》| ★★★★★
  • 重温:电影 |《星际穿越》| ★★★★★
  • 重温:电影 |《盗梦空间》| ★★★★★
  • 重温:电影 |《信条》| ★★★★★
  • 玩过:Steam |《Thronefall》| ★★★★★
  • 玩过:Steam |《时之形》| ★★★★★
  • 在玩:Switch |《塞尔达传说:王国之泪》| ★★★★★
  • 在玩:Steam |《潜水员戴夫》| ★★★★☆

重点安利一下 NExTStudios 和 oooooohmygosh 老师出品的小游戏《时之形》

《时之形》由15个场景构成,参考了众多哲学家对时间的思考,引用官方的一句文案来介绍:“哲学常以文字叙述,时间则常以数字时钟定义,两者都难以具象。不限于语言,《时之形》借助运动、反馈、视听给时间和哲学更丰富而具体的感知。在体验《时之形》的过程中,你仿佛看到、听见并触摸时间的形状,甚至开启生命和哲学之思,尤在夜深人静之时。”

思考是有限的碎片,时间是连续的情感。与其挣扎求索,不如素然以对,或许那样才能画出美丽的生命轨迹。

月刊(第21期):快节奏时代下的短视频

本篇是对二〇二三年四月生活的记录与思考。

🚀 快节奏时代下的短视频

关于周刊选题我有一个 LogSeq 的白板,当我发现某个选题的素材准备的差不多时就会拿出来写写分享一下,也算作话题的总结。写了一个题目就在白板里划掉一个选题,剩余的选题储备长期维持在 5 个左右。

而短视频这个话题在我的选题白板里已经躺了大半年了,每次周刊想写它都会发现准备的还不够充分,写起来会比较复杂,就一直拖着不写,长期躺在白板的角落里。这次之所以决心拿出来写,主要也是前段时间的一些新闻和 B 站的商业化让我有些感受,想写出来分享下。

注意力的散失:倍速播放、三分钟电影、抖音神曲

移动互联网时代以来,我们几乎无时无刻都不在接收新的信息,各种热搜、头条、热点 Push 充斥着眼球。同时人人都有选择,导致信息方向四散。经过这种日积月累的刺激,让我们的注意力变得越来越亢奋。随着时间推移,人们能够集中注意力的时间是越来越短的,注意力容易散失、难以被捕获专注。 我们越来越不满足于慢吞吞的、起承转合的内容和信息,而是希望在更短的时间里面,获得更多的新鲜刺激。 因而,一个极其适合这个时代的产物就应运而生了:倍速播放。

而我个人非常讨厌倍速播放,我认为影音剧集所要呈现的是导演、编剧想要给我们讲的故事,是影像与声音高度浓缩的内容产物,它将创作者对人生的思考、对理想世界的想象等信息压缩在数个小时的画面里面里,因此需要观众慢慢欣赏。其中音效、鼓点、节拍、表情变化、剧情张力都是依靠屏幕内外唯一可以穿越的真实——时间的流动——让观众尽可能确切感受到屏幕内的故事。

比如《星际穿越》主角团探索高重力的米勒水星,BGM 里杂夹着秒表的滴答声,暗示着米勒星球的时间流逝缓慢,一小时等于地球七年,从而塑造主角团需要去争分夺秒的紧迫感。除此之外的例子还有很多很多,这里就不展开说了。如果一部片需要倍速去看,那可能说明它的信息容量不够、或者是不够精彩深刻,那也没有必要去观看。

然而短视频领域里,对快节奏还有更极端的呈现方式——三分钟电影。

三分钟电影解说既不是电影,也不是解说,它更像是一管管高度提纯的多巴胺,通过眼睛「注射」到你的身体里,让你享受短时间摄取超量信息的快感,而代价则是慢慢失去与角色共情的能力,失去欣赏光影、音乐的耐心,最终好不容易培养起来的观影能力退化得一干二净。——「小帅和小美」,用三分钟毁掉电影 | 爱范儿

在这三分钟的电子榨菜里,精妙绝伦的镜头设计直接被抽离,只留下猎奇的画面抓住观众的眼球;跌宕起伏的背景音乐也被换成了激昂慢摇舞曲,刺激你不断分泌肾上腺素提高你的专注度;充满张力的剧情变换也只剩下突兀的猎奇。一切的一切,都在为我们的眼球服务,因此信息浓度高到极致,时长尽可能短暂,AI 的台词节奏也被加快,生怕你下一秒离开了这里。

而我们貌似非常喜欢这套,因为它总能抓住我们孱弱的注意力,它不会让我们的注意力轻易散失,一直一直被抓住,着了魔一样不断往下滑动,于是,我们的时间也被它偷走了。

除了电影剧集,音乐领域也难逃魔抓。

要论播放量,周杰伦的歌可能现在可能还还真的不如动辄破十亿的抖音神曲。当下流行的歌曲,比如抖音神曲,跟过去的流行金曲的根本区别在于:音乐不再是审美导向,而变成了用户行为导向。抖音神曲已经有着成熟的批量制造流水线,从热点捕捉、创作编曲、到录制上线,整个过程只需要花一天时间,每个环节都通过大数据精密计算,务求每个节拍都踩在用户爽点上。

音乐心理学领域的同行评审期刊 Musicae Scientiae 2017 年的一篇研究中,分析了 1986 年至 2015 年的 303 首美国 Top 10 单曲,发现在 1980 年代歌曲前奏普遍超过 20 秒,如今基本已经缩短到 10 秒之内。一首歌的平均长度,从以前的4分10秒下降到大约 3 分 30 秒,2021 年美国 Top 50 歌曲平均时长更短,仅为 3 分 7 秒,其中 38% 的歌曲甚至不到 3 分钟。这表明人们的关注周期越来越短,如果不能很快听到精彩部分,就会感到不耐烦、不再听下去。

最近很喜欢听的蔡健雅的《达尔文》 ,前奏 28 秒,歌曲时长 4 分 25 秒,还有经典的《爱在西元前》,就像音乐制作人陈稷坤说的,它一个前奏就可以让人记住一辈子。而现在很多的编曲,一上来就是噼里啪啦堆一堆,因为今天要吸引用户完整听完一首歌已经不容易了,只要有 15 秒洗脑的片段,就可能一炮而红——比如《热爱 105 度的你》,听过一遍很上头于是脑海里一遍又一遍回绕的那个旋律,想脑子中了病毒一样挥之不去,可过段时间之后什么都记不住,除非再去听一遍。

而我们可以做的应该是慢一点、耐心一点,把注意力从瞬息万变的外界抓回来,不要被外界的信息爆炸所吸引,更多地关注自己的内心,关注你每一天的感受,每一次行动的手感,每一次复盘的反馈、思考、经验。

快节奏时代下的文化狂欢:熊猫、淄博、毒鸡汤

根据巴赫金对狂欢的理解,狂欢式可以分为以下四种范畴:

  1. 人们在狂欢节中可以随便亲昵的接触;
  2. 插科打诨的相处方式;
  3. 人们平等亲昵的生活态度;
  4. 粗鄙,即人们保持的一种平民化的生活格调。

此外,狂欢具有两大外在特征,即全民性和仪式性。根据巴赫金的狂欢理论来看,短视频实际上也属于文化狂欢的一类。

典型的例子是这段时间全网对于熊猫的异常关注,我之前喜欢看熊猫的图片单纯只是因为可爱(尤其是还没有脖子的花花),但是从日本送别香香上了热搜之后,关于熊猫的传播风向和关注热度就变得不对劲了起来,这里就不展开说了。

还有便是突然爆火的淄博烧烤,很多传播学的媒体评论文章去分析淄博为什么突然火起来了,这种热度能否形成模式并复刻。比如全媒派的这篇文章《全网打卡淄博烧烤:短视频造神、社交平台种草和网红城市的网感》 里面谈到了两个点——短视频的“网感”和“在场感”。

短视频的可见性:呈现具有丰富细节的现场,具有视觉冲击力,甚至还带来滤镜、特效等修辞手段,但这种强冲击、奇观化的视觉维持难度较大,这就势必要进入“造神”2.0阶段,即由感性、狂欢、娱乐的体验转向一种意义生产过程。

网感相关的论述如下:

所谓的“网感”,是指基于互联网文化的信息传播、连接方式而建立起来的认知习惯和表达方式。“网感”就意味着网民的所思所想得到关注,体现为碎片化、娱乐化、年轻化的内容气质,有一定的情节爆点和情感痛点,有较强的用户参与性和体验感。

短视频同时具有可见性和连接性,因此制造“在场感”是它擅长的功能,“由此营造沉浸感,在一定程度上留住用户注意力和用户时长。在碎片化、巨量内容的媒介生态环境中,增强受众的场景化体验是提升传播力的关键。”(出处同上)

而短视频“毒鸡汤”的火爆也印证了“在场感”理论的可靠性。如果我们站在上帝视角看,我们很容易辨别出“毒鸡汤”里儿媳打婆婆这些离谱的故事是虚构的,但刷视频时,往往会代入其中,甚至信以为真,这便是“在场感”的作用。

与文字相比,短视频通过人物的演绎及还原,更容易营造一种“在场感”。这种体验仿佛身临其境,参与了一场家庭争吵、参与了一场友谊崩溃,配合夸张的语言文本、戏剧化的人物动作和表情,超现实的拟像状态会让很多用户越看越爽,欲罢不能。

尼尔·波兹曼曾经说:“现代技术彻底改变了人们对于信息的态度,过去人们是为了解决生活中的问题而搜寻信息,现在则是为了让无用的信息派上用场而制造问题。

“毒鸡汤”一味追求超越常识乃至反常识,制造奇奇怪怪的问题来吸引眼球,这种新奇性既吸引着人们,也对人的认知产生了巨大影响。

由于短视频传播主体的泛化,传受双方的交互,最终共同促成了以上这些狂欢现象,而狂欢本身又会弱化、消解人们对严肃问题的认知。

因此最后想再结合 B 站现状谈一谈现代人正在进行的危险游戏。

现代人的危险游戏:解构神圣,执迷现象

前段时间关注到 B 站 Up 主停更事件,木鱼水心发了一段回应——

“接下来,我们会制作越来越多的节目。在不放弃深耕长视频的同时,去拓展更多的节目形式,也会思考和尝试更多的商业可能性。

「脚踏实地,认真做内容」,这不应该成为一个悲壮的故事,而应该成为一个「站着活下去,并且活得更丰盛」的故事。

我常常想,在这个年代,能够产出这样的作品,和这么好的观众们互动,难道不是最最幸运的事情吗?”

于是我去关注了下 B 站的商业化模式和短视频战略的分析,对于花火平台的问题、广告收益的问题、用户画像的差异这里不展开说了,不是本文的重点,相关材料列在了附录的参考资料里了,有兴趣的同学可以自行了解。这里想重点谈谈认真做长视频 Up 主在这个时代下被逐渐淘汰的悲哀。

目前无论是长视频还是短视频,无论是文章还是传播的营销活动,能够踩到观众爆点的现象级的话题,最终都服务于流量传播,无外乎有三类:

  1. 能引发了大众焦虑,比如赚钱、贫穷、两性、学习、工作之类的话题,如探讨应届生就业难、裁员潮、分享副业、下乡种田等。
  2. 直击社会热点,义愤填膺一番,带有明显的情绪渲染和对猎奇心理的迎合,如丫丫、拖夫等。
  3. 能让受众感觉到优越感的,比如网易云各种哄人的测试。

不妨静下心来想一想,我们真的需要这种内容吗?我们学习知识不是为了制造流量凸显优越感、也不是为了抓人眼球骗人点击,而是去传递知识。

越是在注意力被分散的浮躁时代,越是需要更多优秀创作者以优质内容破壁,沉下心去打造具有陪伴感的内容输出;越是丧文化风靡,就越需要更多明丽的热血瞬间去破穿时代空气里的灰雾与阴霾。

不可否认,现在的社会生活变得丰富多彩起来了。现代人心安理得地陶醉在快餐式的消费文化中。现代人不需要思考本质,他们只相信现象,因为他们看穿了本质只不过是人自己虚构的一个幻象。现代人太清醒了,哲学的斯芬克斯之谜已经不可能再让他们感到困惑,因为他们根本就不需要去思考那些稀奇古怪的东西。活着,并且快乐着,这就是现代人的生活秘诀。

不过这真的是“快乐”吗?

短视频、各种娱乐游戏和活动,这些快乐不需要思考,来的非常容易,但也容易让人迷失。我们费劲心思寻求快乐,可是快乐好像成了速食快餐品,似乎少了一些什么。

短视频让我们短暂沉浸在低级快乐里,忘却了颓废的不想努力的自己,给自己营造了一种虚假的充实,实则最后只会带来更多的空虚与不安。而那种持久思考,需要艰难的付出获得的快乐,是这些东西没有办法替代的。

今天的人类正在进行一场危险的游戏——抛弃了一切神性的东西、本质性的东西,总觉得没有什么东西是崇高的,解构着神圣,嘲笑着深刻,把自我意识和当下感受提高到了无以复加的地步。现象学与存在主义大行其道,主张抛弃一切本质或深刻的东西,跟着感觉走,尽情地去享受当下的生活。

事实上,当我们放弃本质、追逐现象的时候,当我们以为自己变得聪明的时候,我们已经走上了一条从人到动物的道路。

参考资料与扩展阅读:

🎬 本月书影音

  • 在读:哲学 |《西方哲学讲演录》(赵林)| ★★★★★(5.0)
  • 在读:哲学 |《哲学与人生》(傅佩荣) | ★★★★☆(3.5)
  • 在读:文教 |《人类简史》 | ★★★★☆(3.5)
  • 在读:文教 |《自私的基因》 | ★★★☆☆(3.0)
  • 在看:动漫 |《鬼灭之刃:锻刀村篇》| ★★★★★
  • 重温:英剧 |《神探夏洛克》| ★★★★★
  • 重温:电影 |《活埋》| ★★★★★
  • 看完:电影 |《流浪地球 2》 | ★★★☆☆
  • 看完:电影 |《长空之王》 | ★★★☆☆

本月阅读较少,下个月要弥补下阅读方面。By the way,安利下赵林的《西方哲学讲演录》,找回了自己当年考研读到他和邓晓芒编写的教材时,所感受到的那种对哲学的真挚的热爱,这才是真正值得接触的哲学。平时也没有在看剧和玩游戏了,一方面是没有发现好看的剧,另一方面是调休有些打乱节奏,项目也处于关键时期需要补充的知识不少。

本月做了一个 GPI 测试,具有强烈的成功愿望(9.8/10分),情绪调控也是符合预期的高达 9.8 分(毕竟五一开车堵了 9 个小时还是能保持平静,笑),批判性和条理性得分也很高,但是乐群性、适应性等人际/环境上的指标只有一点几分,需要后续加强。

已经度过了一个春意酥怀的四月,期待下个月能安心玩上《王国之泪》~

月刊(第20期):重启人生

本篇是对二〇二三年三月生活的记录与思考。

🎬 本月书影音

  • 在读:小说 |《德米安》 | ★★★★★
  • 在读:小说 |《少年巴比伦》| ★★★★★
  • 在读:文学 |《西方文学史》| ★★★★☆
  • 看完:日剧 |《重启人生》| ★★★★★★
  • 看完:韩剧 |《黑暗荣耀》 | ★★★★★
  • 看完:美剧 |《最后生还者》| ★★★★★
  • 看完:电影 |《情书》| ★★★★★
  • 看完:电影 |《保你平安》| ★★★★☆
  • 在看:动漫 |《钢之炼金术师 FA》 | ★★★★★

这个三月基本没有玩游戏,周末都在看剧,从《最后生还者》到《黑暗荣耀》,再到《重启人生》。一部部来说吧,先从心目中最佳的《重启人生》开始。

如果只打五星已经不能满足我对《重启人生》的溢美之词了,所以这次给了史无前例的六颗星!虽然才 3 月,但我觉得《重启人生》已经是今年的年度最佳了。主角麻美意外去世,得知「下辈子将投胎为大食蚁兽」,若想投胎成人,需要不断重启人生,积德行善。在部重生剧在真正意义上做到了“清新脱俗”,简单谈一谈它的优点吧!

一,细腻的真实感

开篇是麻美的第一轮人生,影片看了四十分钟全是琐碎无聊的日常小事,比如和同事讨论在市公所上班需不需要说“欢迎光临”,给朋友庆生没有准备生日蛋糕、服务员会不会有什么想法,分析爸爸的私房钱藏在哪。整篇故事文本量巨大,充斥着大量的对白,但都在事无巨细地呈现主角的日常琐事,似乎是一些「没用的废话」或者「无聊的思考」,仿佛已经忘了“重生”这件大事。第一集的观感让我感到平淡,甚至有些无聊。但这种细腻的剧本,角色们对生活琐事的碎碎念、以及每个角色特有的小细节,这些闲笔制造真实感,逐渐会让观众接受他们是自己珍贵的朋友,对她们的命运产生共情。

至第三轮时,仿佛看到了自己的影子,虽然每天工作很忙、加班到很晚,但会有好友半夜跑来看你,这种感觉让人觉得甚是幸福。

而至第四轮时,则更充斥着苦涩的现实,如果重生那么多次和最好的朋友、和亲密的家人疏远了,那下辈子即便能够做人还有什么意义呢?在麻美重生四次的百年之后,最想念的仍然是童年时互换贴纸的那个遥远下午,而对于观众而言亦是如此。

冲破文本内部的情绪惯性,抓住人物当下细微的思绪,这种真实感流露出细腻的情感,让我在不知不觉中也视她们为真实的朋友。

二,平视每一个角色,不戏谑任何一种价值观

这里要说到剧中的配角小福,他对自己的音乐才华始终有过高期待,事业不顺,直至离婚,离婚后在 KTV 当服务员,按说很可悲,但编剧对这个人物的塑造始终有种潜台词——任何人都没资格评判或怜悯他的人生。加缪也曾言:“应当想象西西弗是幸福的。

麻美第二轮时曾想过改变小福的人生轨迹,让他的人生变得“更好”一些,但最终她没有去实施,多年后在 KTV 和小福聊天,确认了他对自己当下的生活很满意,确认他洋溢幸福的笑容,麻美对自己的决定如释重负。

平凡并不等于平庸,因为一个人的人生,如果是平凡的人生,至少可以活成自己接受的样子;但是一个平庸的人生,一不留神就会活成自己讨厌的样子。小福是平凡的打工人,但是他至少在努力地、幸福地活着。

这就要说到本剧背后那个更温暖的东西——活着不是快乐,快乐才是活着

三,更温暖而强大的内核

这部剧集给我们呈现了人生的本来面目,但它同时有着一些更丰富、更简单、更强大的东西。

好的作品亦是如此,它不是为了让观众重温现实,而是从现实中创造出一些东西,把一种更强烈的情绪传递给观众。

而时下不少作品充斥着傲慢和对角色、甚至是对观众的戏谑,畸形的时尚造就虚伪。它们把世间一切自然的东西,无论是物质的、情感的、还是伦理的,都当做世仇而加以割舍、抛弃、毁坏,并且认为割舍得越彻底,心灵就越崇高,结果只能导致作品与现实世界的背谬。对肉体和自然人性、人情的无端摧残是不能构成审美愉悦的,由此而获得的沐神福祉的喜悦也是一种畸形的心理病态。

但人在此世间,并非仅仅作为个人而存在,他同时也是独一无二的特殊个体,永远是一个关键而奇妙的点,在这个点上,世界的万千现象纵横交错,充满不可重复的偶然

应了《日常》里的那句话:我们所度过的每一个日常,都是一个个小小奇迹的连续

而我们要做的,只是体会那情感的任性,以把捉自由、探险人性

在这每一个平淡的日常之中,《重启人生》以善良底色书写不说教的故事,赢得喝彩。它承认个体力量的有限,哪怕读档重来,每次也只能进步一点点,但我们仍能通过点滴的小小努力,给自己的人生抛个光。

当然我最喜欢的还是这篇故事最温暖的结局,看到最后会有一种时间都停了、所有人都回来了的感觉,经过了百年来的兜兜转转,终于留住身边最好的人。——与其一个人变成人,不如一起变成鸽子!

PS: 剧组内的氛围也有爱,番外篇值得看一看!

除了《重启人生》,本月看完的《最后生还者》和《黑暗荣耀》也都是满分作品,两者都有着反传统的结局(复仇成功、没有原谅加害者、没有“大义”的介入),总体观感很爽很过瘾。

《最后生还者》还原原作,但适时加入一些轻微的调整和战斗情节的略过,避免剧集沦为游戏那样平铺直叙的公路片。原作原本就是神作,因此忠于原作的剧集也很棒,当然演员表现也棒,乔尔和艾莉的演技令人惊叹。

《黑暗荣耀》相较于它的复仇剧情,我更喜欢它的镜头语言,比如片中不少霸凌情节穿插了第一人称视角,增强了观众的代入感,更易产生共情,让后面的复仇更加爽快;在月色中女主漏出全身伤疤时房间的明暗对比、还有围棋师徒四季榕树的变化之景,也都是片中绝美的镜头。除此之外,剧中部分音效用的也非常不错,值得琢磨一番。

🌺 本月生活点滴

🏸:这个月开始打羽毛球了,自从去年年底疫情肆掠之后就没有打过了,现在重新捡回这项运动,并通过它遇见有意思的人们,是一件幸事。

🚴🏻:这个月开始骑自行车上班,通勤耗时比开车要短一些,并且每天还能省下不少停车费。By the way 电助力车骑在路上就像开挂一样。

👩🏻🍳:在B师傅的教导下学做菜,目前学了电饭煲鸡腿、白灼虾、耗油生菜这种简单的菜肴。(照片就不放了,能吃就行。)

🦆:某个周末的下午在家楼下的河边发了一下午的呆,放空自我,盯着看白鹭是怎么捉鱼的,看流水的形状,感受时间从指尖流过,忽然有那么一瞬间貌似能体会到钓鱼的乐趣了。

👨💻:对工作内容有些迷茫,某个周日找了B前辈吃饭聊了聊,被指明道路之后一切都豁然开朗了,是自己太着眼于当下与急功近利所以才会迷茫,于是回家之后列出了未来7年每一年的职业目标,按这计划一步一步脚踏实地向前走,终有一天能登上顶峰。By the way,那天下午去和朋友试驾了阿维塔,试驾的工作人员给了我很深的印象,虽然是位零几后,但是对工作非常认真负责、富有激情,同时他也对我说到,工作就是要怀着热情且谦卑的态度认真去做,该工作的时候就不要考虑别的,把事情做得漂亮自然就有好结果。

⛺️:组内团建去梧桐山脚下露营,虽然很堵车,但是敞篷在山里派上了用途。By the way,篝火晚会还是蛮有氛围的。

总结:这个三月过得很开心、很充实、很满足,是工作以来最满足的三月了。希望四月是一个繁美丰盛的四月,一见倾心的四月、春意酥怀的四月。💐

月刊(第19期):日记的意义

日记的意义

月底在回顾日记以准备当月的月刊素材时,偶然在 dayone 上看到了17年-19年2月28日的记录,而在 daylio 上则有着20年-22年2月28日的记录。

这才发现,写日记这个习惯居然已无声无息持续了已六年。再回头看这六年间的日记,着实有一种奇妙的感觉。于是这篇文章就来谈谈——日记的意义吧!

于我而言日记的意义有以下几点:

  1. 自我觉察的工具
  2. 折射生活的意义
  3. 记录身边的美好

1. 自我觉察的工具

自我觉察指的是察觉自己真正的感受和情绪,理解自己真正的需要和欲望是什么,从而为生活找到目标和意义感。

记日记的好处之一是可以通过日常细碎的思考来了解自己的思维,类似于辅助正念的工具,通过觉察自己的情绪,从而发现自我。

前几年上学的时候也开发过一款 App 叫「双生日记」,主打的匹配和心理报告功能也都是为了自我觉察而服务的。

2. 折射生活的意义

其实这一点本质上和“自我觉察”说的是同一件事,因为自我觉察本身就是找寻意义感的重要途径。这里单独拆出来自成一节展开谈谈。

在之前的一篇文章中(《谈谈人生的价值与体验》)谈到了”人生是一连串的刹那,最重要的是当下的此时此刻” ,而“生命的意义并不是由所谓的“人生大事”来赋予的,而是落实到生活中无常的琐碎”。

我们求而不得的生活意义感,首先是通过这样真实地投入生活开始的,只有当你“真实地存在”的时候,你和生活才建立了足够深入和真诚的链接,生活会用它独特的方式给予你回报。

日本茶道中讲“一期一会”,意思是在茶会时领悟到这次相会无法重来,是一辈子只有一次的相会,故宾主须各尽其诚意。这也是佛教中“无常” 的思想,珍惜每个瞬间的机缘,并为人生中可能仅有的一次相会,付出全部的心力

眼前可口的饭菜,河里跃起的鱼群,路边飘落的花瓣,每个时刻、每件事物都会让我们有发自内心的喜悦,油然而生的幸福感,即使是恐惧或愤怒也是当下的,我们有的只是当下的体会和感受。觉知当下的喜怒哀乐、体验它们、悦纳它们,便是活着的意义吧。

而日记的作用便是记录这每一个“当下”的觉察和感受,虽然这些念头可能以后永远都派不上用场,但是光是记下来本身,就是生活意义的折射,能产生一种充实的幸福感——至少当下的心情是真实的,当下的瞬间没有被浪费。

3. 记录身边的美好

这个点记录了 4 年日记之后才发现的,如果真的要记录点什么,为什么不能只记录那些开心的事情呢?

在去年的年终总结里提到了“在睡前回顾今日的心情时,也会选择性地去记录今天遇到的美好的事情,一方面是期望带来好梦,另一方面是期待明天可以更美好。”日后翻看回忆时,也尽是美好事物,这不是一件很美好的事情吗?

愿我们不会将一生都花费在漂浮奔波上,而是怀着对生活的喜悦和热情,全情投入到美好生活中。

最后摘录一段 2019 年 WWDC 19 游记 中的一段话作为结尾:

本月记录

月初出差去北京听 GMTC,这个会的北京站也是比较坎坷,从2020年开始数十次因疫情推迟,最终到23年才……这也是我工作以来第一次出差。

在北京约到了久别重逢的好友,是很久以前最好的朋友,转眼已整整5年未曾联系,本以为老死不相往来从此沦为人生遗憾,但再见时已记不清为什么要不相往来。真的是一点儿也没有变。

贴一些 2 月的瞬间:

P1: 🍶 夜晚,清冷的北锣鼓巷。没有破败的气息,只是清冷,寥寥几盏灯火点亮幽邃的胡同。喜欢“不卖书的书店”和专注巧克力的“可可芭蕾”。

P2: 🍻 有人跳海,胡同深处的一家小酒吧。那晚在无尽的黑巷里走了一个多小时,才觅得的一家可以进去避风休息的店铺。从北京回来之后没几日发现了一直关注的博主也去了这家店(下图也是从他的周刊里盗的),有种时空交错的感觉。想吐槽下北京的店面关门都很早,晚上八九点就没有什么店了,十点多连车都打不到了 QAQ

P3: 🌺 楼下的鸡蛋花树开花了,春天来了。很喜欢家楼下的这条马路,傍着小河,静谧且美丽。

P4: 🍩 海边的落日与飞机

P5: 🚗 难得去广州,但来回用了将近8个小时,疯狂堵车的一天。灾星体质,去哪儿哪儿就会发生车祸。

P6: 🍾 周五晚邀请朋友来家里吃零食、吐槽、看电影

P7: 🎮 做原神任务时发现的一个超美场景

P8: 📮 自从上期提倡信件交流以来,又多了些好友来信,上月往来信件 20 封,发现信件交流确实更加沉静、深入。也欢迎读者给我写信—— airing@ursb.me ,什么乱七八糟的都可以交流~

P9:📝 关于工作,做了一个部门分享,写了一篇技术文章,在知乎接了一个软广,学习了 Android JNI 编程,给项目的 V8 接入了调试器。

P10:🎬 本月的书影音(貌似是最多的一个月)

  • 读完:小说 |《一个陌生女人的来信》 | ★★★★★
  • 读完:小说 |《消失的13级台阶》| ★★★☆☆
  • 在读:小说 |《德米安》 | ★★★★★
  • 看完:电影 |《蚁人与黄蜂女 3》| ★★★☆☆
  • 看完:电影 |《饥饿站台》| ★★★☆☆
  • 看完:日剧 |《凪的新生活》 | ★★★★★
  • 在看:美剧 |《最后生还者》| ★★★★★
  • 在看:剧集 |《三体》 | ★★★★★
  • 在看:动漫 |《钢之炼金术师 FA》 | ★★★★★
  • 在玩:Switch |《女神异闻录 P5R》| ★★★★★
  • 在玩:Steam |《荒野大镖客 2》| ★★★★★

大厂自研跨端框架技术揭秘

导言

本文将围绕跨端框架技术的主题,分析其技术目标和 3 种演进方向,接着揭秘业内的自绘跨端方案的技术实现——包括 Kun、WebF、TDF、Weex 2.0、Waft 与 MiniX 等方案,分析各自的特点与不足,总结跨端框架的研发思路与技术要点,最终分享对跨端框架发展趋势的思考。

分享过程中,会穿插介绍跨端框架的脚本引擎的选型与技术难点、业内各跨端框架各自的困境、分享 Debugger 原理以及一核多生态的工程化思路。

大纲如下:

  1. 跨端框架的技术目标(略)
  2. 跨端框架的技术方向
  3. 跨端框架的技术揭秘
  4. 跨端框架的技术要点
  5. 跨端框架的发展趋势(略)

注 1:本文系线下分享的文字总结版,将省略前情提要、技术背景(如第1节、第5节)与部分技术细节的扩展(如第3-4节部分内容),仅保留核心内容。

注 2:本文材料源于 GMTC 大会跨端主题的公开分享、部分企业的公开微信公众号文章、框架公开源代码与个人历史分享素材,其余内部分享与内部框架等材料做脱敏处理。

跨端框架的技术方向

我略微总结了一下,跨端框架有以下 4 种技术方向。

方向 1:基于 WebView 的增强

基于 WebView 的增强是一个偏前端往客户端方向靠拢的技术方向,即上层生态依然使用 Web 技术,但是需要依靠客户端对 WebView 做一些能力补充。

比如:

  1. Ionic、Cordova 等 Hybrid 框架。
  2. 业内一众基于 WebView 的小程序。
  3. Sonic 等客户端预加载 WebView 资源的方案。
  4. App 厂商针对 Web 做的离线包方案。

这类框架都有这几个特点:

  1. 基于 WebView 渲染,但补充了一些原生能力增强
  2. 开发生态基于 Web 前端生态(严格来说,小程序也是)
  3. 想方设法增强 Web 的用户体验

方向 2:基于 DSL 的 Native 增强

基于 DSL 的 Native 增强属于偏客户端但往前端方向靠拢的技术方向,即开发生态基于 Native,但是框架设计上参考了 WebView 的一些特性。这些框架总体上都是自定义了 DSL 来实现跨端与动态化的。

比如手淘的无障碍框架 DinamicX、美团的 MTFlexbox、阿里的 Tangram(七巧板)。

浏览这些框架的文档可以发现,它们的设计比较像 React Native,只是上层需要开发者使用 DSL 来接入组件。

方向 3:代码共享

代码共享是终端的开发方案,目前业内熟知的方案是 KMM(Kotlin Multiplatform Mobile),通过 K2 编译器将 kotlin 源码编译成各个平台的目标代码,从而实现跨端。具体而言,kotlin 通过编译器前端生成带有语义信息的 FIR,之后 FIR 交给各个平台的编译器后端来进行优化和生成,如 JVM/LLVM 等,最终生成各平台可执行的目标代码。

KMM 的工程结构也比较简单,包括跨端代码(Shared module)与壳工程(Android/iOS App)两部分组成。

目前来看这套方案在生成 iOS 代码时,对多线程的逻辑处理不是特别好,需要业务方优化。

方向 4:基于 GPL 的 Native 增强

基于 GPL 的 Native 增强我将其视为大终端的开发方案。所谓大终端是一个融合之后的产物,在早年 PC 时代,大家使用 .NET、JVM、Qt 来开发桌面应用,我们将其称为终端开发;随后进入移动端时代,Native 方向的客户端开发在不断追逐动态化之路,而跨平台方向的前端开发在不断追逐性能之路,这两条道路最终融合成如今这些跨端框架。无论是小程序、Flutter、DSL 开发框架、WASM 均属于融合演进的产物。

总结一下,方向 4 有以下几类方案:

  1. 原生渲染组件:如 React Native / Hippy 1.0 / Weex 1.0 等。
  2. 自绘引擎:Flutter。暂且将其单独算作一档。
  3. 基于 Flutter 的自绘框架:这里业内有诸多框架(曾整理过 20+ 框架),如 WebF(Kraken),Kun,FMP,基于 Skyline 的小程序等等。
  4. 基于系统图形库(Skia / Vulkan / Metal / OpenGL)的自绘框架:这里业内也有不少框架,如 TDF,Hippy 3.0,Weex 2.0,Waft 等等。严格来说 Flutter 也属此类。

前 3 个方向以及第 4 个方向的前两类框架都是开源的,且业内也有不少文章介绍了它们的原理,这里就不赘述了。本文主要介绍第 4 个方向后两类框架的技术方案。

跨端框架的技术揭秘

本节将挑选几个有代表性的框架做技术揭秘:

  1. Kun
  2. WebF
  3. Weex 2.0
  4. TDF(需脱敏,略过)
  5. Waft
  6. MiniX(略)

之所以在芸芸框架中挑这几个,是因为他们的方案在领域细节中属于典型框架,可关注这些框架的应用开发体系、脚本引擎与渲染引擎的选型。

Kun

Kun 是闲鱼基于 Flutter 开发的一个跨端框架,目前并未开源,网上能学习到的文章只有闲鱼公众号上发表的三篇文章。架构设计比较简单,虽然没有源码也能分析一二,架构图这里就不放了,有兴趣的同学可以自行点进文章了解。

Kun 的整体设计思路是基于 Flutter 开发一个 JS Runtime,开发者使用前端生态进行页面开发,JS 解释器作为胶水层会将源码翻译成 Flutter Widget,之后交给 Flutter Engine 做渲染。

JS 引擎他们采用了 QuickJS,但猜测应该是阿里内部的 QKing 引擎(基于 QuickJS)。

Debugger 支持 CDP,Test 基于 Flutter Golden test。

CSS 解析他们先使用 Yoga 做 polyfill,将样式处理成 css in js,之后解析模块挪用了 Kraken 的遗产 CSSLib,通过 Dart FFI 将 JS 测的内链样式传递给 Dart 侧做处理,最终解析成 Flutter Widget。

但是 CSS 的盒模型与文档流毕竟与 Flutter Widget 的样式标准格格不入,他们则采用了 Widget 拼接的方式,每一层 Widget 特定处理某类样式,最终通过层层套娃拼接的方式实现组件样式。如下图所示,这是一个 div element 所对应的拼接方式:

总结一下特点:

  1. 不支持完备的 W3C 标准(也不可能支持,比如 css in js 无法实现伪类),只支持各标准子集,包括:HTML 标签、CSS 样式集、WebAPI 标准
  2. 提供了一些定制的 Element 组件,由 Dart 侧实现,业务方也能使用 Dart 侧来开发一些定制的 Element。
  3. 组件的实现上采用 Widget 拼接的方式

本节参考资料:

WebF

WebF 前身是阿里的 Kraken,后团队解散部分遗产交接给了 Kun,剩余同学出走在开源社区成立了 openwebf,将 Kraken 改名 WebF 继续维护。

这个是 WebF 的架构图:

可以看到与 Kun 不同的地方在于除了提供了 JSBinding 之外,团队还在 Flutter 的 Dart 侧做了一些开发,将 RenderObject 的能力做了丰富,以适应 W3C 标准——即在 Dart 层来实现 CSS,C++ 层实现 WebAPI,对标 W3C 标准。

脚本引擎依然是 QuickJS,但是目前做了一些优化,值得学习一波。

其实对比一下 Kun 和 WebF,我们可以发现他们对 CSS 的处理采用了两种不同的思路。

先说说 Kun 吧,它的方案存在几个问题:

  1. 一条渲染链路存在两次 Layout,这是完全没有必要的,而且 Layout 的更新频率本身也非常高,两次 Layout 会带来额外的性能开销
  2. Dart FFI 不足以支撑样式更新的信息传递,样式更新的数据量很大,会触及 FFI 的瓶颈
  3. 内联样式的开发体验不好,很多 CSS 的属性也会无法实现

那么 CSS 应该如何实现呢?有两种比较好的解法:

  1. CSS 在 Dart 层实现,样式更新依靠 RenderObject 的 Layout,无需走 FFI
  2. DOM 与 CSS 全使用 C++ 实现,剥离 Dart 层

解法 1 便是 WebF,解法 2 是后文的 Weex 2.0 与 TDF 等框架。

但解法 1 也存在技术难点,因为引入了 CSS 会导致 RenderObject Tree 难以维护,那么我们应该如何管理 RenderObject Tree?这也有两种思路:

  1. 把 RenderObject 做薄:即 Flutter Widget 做原子级渲染组件,不对 RenderObject 做修改,上层通过组合 RenderObject 来实现复杂功能和样式。就像 Kun 那样。
  2. 把 RenderObject 做厚:集成大量的布局渲染能力于一身,上层通过样式表驱动 RenderObject 渲染。

显而易见的,把 RenderObject 做厚会是更好的方案,因为前者复杂度太高(看前面那段层层嵌套的代码也可以直观感受到),每个样式规则的计算都需要一层一层检查推断,导致维护效率下降。

因此,这里我比较看好 WebF 的方案,并且 WebF 也是目前众多跨端框架中唯一一个拥抱开源的方案,呼吁有兴趣的同学加入 TSC 一起共建。

本节参考资料:

Weex 2.0

Weex 2.0 是阿里内部开源的跨端方案,目前基本上实现了阿里内部的一核多生态体系。技术架构上完全推倒 1.0 重新研发,期间他们也走了不少探索之路。从分享来看,整套方案比较完备,工作量也很大。

这个是 Weex 2.0 的结构图:

重点介绍一下这几个组件:

  • WeexAbility:容器和能力扩展,URL 拦截、缓存、基础 API、三方扩展等。
  • WeexFramework:通用基础框架。封装页面实例,实现 DOM、CSSOM、WebAPI 标准,解耦脚本引擎和渲染引擎。
  • QKing:脚本引擎,基于 QuickJS 的魔改。
  • Unicorn:自绘渲染引擎。实现 CSS 能力,包括完整的节点构建、动画、手势、布局、绘制、合成、光栅化渲染管线,可跨平台。
  • WeexUIKit:原生 UI 渲染引擎,封装了原生组件。

2.0 源码产物和前几个框架一样是基于 jsbundle 打出来的 bytecode,但是编译做了一些 SSA 的优化,此外 JS 运行时也做了许多优化,全链路使用 C++开发,没有额外的通信开销、没有冗余的抽象、链路更短,同时基于自研的 Unicorn,有着精简布局算法、精细的操控手势和动画,直通系统图形库。整套方案与 1.0 毫无关系,解决了 1.0 的跨语言通信问题、双端渲染差异问题、布局算法问题、脚本执行效率问题。

基于 Weex 2.0,阿里解决了烟囱式方案的问题,基于多核同构的内核,推动了基础能力的统一,以此来支持差异化的业务场景:

本节参考资料:

  • 门柳:《淘宝新一代自绘渲染引擎的架构与实践》(2023 GMTC)

注:腾讯的 TDF 也在致力于类似的工作,此处脱敏不再介绍。

Waft

Waft 全称 WebAssembly Framework for Things,是天猫精灵团队基于 WebAssembly Runtime 与 Skia 开发的一套自绘框架,没有开源。虽然它目前没有实现框架,只支持 AIoT 的场景,但是原理上是可以跨端的,因此放在这里介绍下,以开阔思路。

天猫精灵早期在 AIoT 上有过一些尝试,最开始做 Android App,但无奈运存太低,只有几百兆,所以性能受限;后续他们开发了云应用,效果虽然还可以,但是服务器成本太高,被叫停;于是继续探索端渲染的道路,研发了 Waft。

这个是 Waft 的架构图:

他们也重新设计了加载流程和渲染流程:

可见整体工作量比较大,并且也不契合前端标准和生态。

这里脚本引擎选型 WebAssembly 他们提供了一张对比图:

这里我对这个脚本引擎的选型是存疑的,想了想有以下不足之处(也可能他们内部有其他考量):

  1. fib 的用例太简单,无法充分发挥 JS 引擎的优势
  2. AOT 来对比解释执行,是明显不公平
  3. QuickJS 应该用的原始版本,它还有很大的优化空间
  4. 用力也没有去对比其他有 JIT 模式的引擎,比如 V8 和 JSCore 这些
  5. 这里没有说明使用了什么 wasm 的框架,因为不同 wasm 的实现性能表现是不同的,有的侧重于解释执行的效率,有的则侧重于 AOT / JIT 的效率

Waft 本身也有的问题,期待他们后续能优化:

  1. CSS 仅支持部分子集
  2. W3C 标准(DOM Elememt、WebAPI)实现欠缺
  3. 包体积可能偏大,这部分先存疑

所以目前的 Waft 的实现也决定了应用场景,暂且只能支持简单的 IoT 页面。

参考资料:

  • 聂鑫鑫:《Waft:基于 WebAssembly 和 Skia 的 AIoT 应用开发框架》(2023 GMTC)

跨端框架的技术要点

动态化

介绍了以上框架,可以总结下跨端框架的应用场景:

  1. 动态化
  2. IoT
  3. Desktop
  4. 车机
  5. 一核多生态

所谓“没有动态化能力的跨端技术是没有灵魂的”,其实我们也可以发现动态化框架和跨端框架很多部分其实是完全重叠的,我之前总结过动态化的五种实现思路:

  1. 基于 WebView 的增强
  2. 基于 DSL 的 Native 增强
  3. 基于 GPL 的 Native 增强
  4. 插件化(Android)
  5. 利用 OC 运行时动态化特性(iOS)

我还画了一张图来补充说明:

注:这张图我画的比较早,其实左上角可以换成 “Flutter 与其他自绘框架”。

他们的核心其实都是要在 Runtime 期间加载可执行代码,并调用。可以发现前三个动态化的思路和我们总结的跨端框架的技术方向是一模一样的。

技术要点个人以为有以下几点:

  1. 脚本引擎
  2. 渲染引擎
  3. 调试器
  4. 工程化

一一来介绍。

脚本引擎

脚本引擎的选型思路有以下三个:

  1. JS 引擎:仅用于胶水语言,对 JIT 不强依赖
  2. Dart VM:主要是为了利用 Flutter Engine 来渲染,因此使用 Dart 生态
  3. WARM: 需要设计 DSL 和实现渲染引擎,完善整个生态

如果选择 JS 引擎,那么也有以下几个选型思路:

  1. 使用双引擎:即各端使用自己的优势引擎,Android 使用 V8,引入 j2v8 即可,而 iOS 使用 JSCore 则完全无包增量。但可惜的是直接使用 JSCore 无法开启 JIT。
  2. 使用 Hermes 单引擎:Meta 为 React Native 这类 Hybrid 框架专门开发的脚本引擎,开箱即用。
  3. 使用 QuickJS 单引擎:大神开发的 JS 引擎,胜在体积极小,性能优秀。
  4. 使用自研 JS 引擎:基本上业内都是基于 QuickJS 做优化的。

小结了一下 QuickJS 目前存在一些问题:

  1. 没有 JIT,这个按需实现吧,有 JIT 虽然执行效率上了一个数量级,但是作为胶水语言而言看重的不是这些。JIT 会导致冷启动耗时增加、内存占用变大、体积变大,而且 iOS 还不能用。
  2. 手动 GC,难以管理和维护,可优化
  3. 缺失行号记录
  4. 缺失 Debugger,目前 github 有一些开源插件实现了
  5. 缺少 code cache
  6. 缺少 inline cache
  7. 缺少内存泄露检测能力
  8. Bytecode 有许多优化的空间

渲染引擎

渲染引擎选型思路有二:

  1. 基于 Flutter Engine
  2. 基于系统图形库,如 Skia / OpenGL / Metal / Vulkan

不管基于啥,框架的整体思路都是精简管线,并使用同步光栅化。

调试器

Debugger 一种可以让 JavaScript Runtime 进行中断,并可以实时查看内部运行状态的应用,是提供开发者使用的工具,作为框架而言必不可少。

目前主要有三种调试协议,刚才介绍的框架都至少实现了其中一种:

  1. CDP: Chrome DevTools Protocol
  2. DAP: Debug Adapter Protocol
  3. 自建协议:微信小程序早期就是自建协议

工程化方案

工程化至少包括以下工作:

  1. 资源加载方案
  2. 降级处理
  3. 版本管理
  4. 研发模式

这里之前 Q 音开发的 Kant 在工程化上有过详细的设计与实现,此处不展开说了。

总结

自绘框架常遇到的问题与解题思路:

  1. 开发体验差:生态使用前端生态,即提供 JS Runtime;需要提供 Debugger;IDE 需要支持语言服务。
  2. 文档写的不好:写好文档。
  3. CSS 能力不够用:对齐标准;如果 Dart 侧实现 CSS,需要把 RenderObject 做厚。
  4. 样式和 H5 不一样:堆测试用例,配合 WPT 验证
  5. Android 和 iOS 不一致:利用已有资源,可基于 Flutter
  6. 组件太少,没有生态:对齐 W3C 标准,尽可能完备
  7. JS 执行性能差:自研 JS 引擎
  8. 不够标准,无法复用社区库:对齐 W3C 标准,尽可能完备

值得学习的一些经验:

  1. 标准至上
  2. 提供丰富的文档
  3. 少自研,合理利用已有资源
  4. 开发体验很重要
  5. 关注低端机表现

参考资料与扩展阅读

  • PPT(已脱敏): https://weekly.ursb.me/slide/cross-end/
  • 门柳:《淘宝新一代自绘渲染引擎的架构与实践》(2023 GMTC)
  • 聂鑫鑫:《Waft:基于 WebAssembly 和 Skia 的 AIoT 应用开发框架》(2023 GMTC)
  • 晟怀:《WebF 是如何高性能实现 Flutter + Web 融合》(2022 QCon)
  • 吉丰:《大终端领域的新物种-KUN》
  • openwebf/WebF
  • Airing:《Kant 在「QQ 音乐」的实践》(未公开)
  • Airing:《Flutter 动态化方案》(未公开)

月刊(第18期):逃离社交网络

逃离社交网络

这个月尝试了“逃离社交网络”,有些收获和心得,本期月刊从“动机”和“方法”两个方面来简单分享一下。

0x01 动机

首先是动机,为什么要做这个尝试?

《周刊(第 9 期):高效率到高消耗的现象与反思》中有说过——「移动互联网带来的更多是消耗,同时上网这件事本身,变得焦虑了」。但后来我又想了下,这个描述不够准确,并非是互联网带来的这些负面因素,准确来说应该是互联网中「社交网络」给我们带来了更多的消耗。

这些「消耗」具体表现在以下三个方面:

  1. 表达力被弱化
  2. 专注力被剥夺
  3. 个体价值被遗忘

首先,表达力被弱化

留心观察社交网络上的文体,包括近几个月来网络依次火过的「疯四文学」、「小狗文学」、「小猫文学」、「疯批文学」、「鼠鼠文学」,皆是套语气、套情境的模版体,这些文体现成的几乎所有段子都没有任何有价值的点可以体现其「表达力」。游离于这般环境的社交网站,由此而来,日积月累,表达能力会受到严重损伤,同时造成线上与线下状态分离。线上可能是疯批文学的段子手,各种文体可以信手捏来,但是线下却写不出一篇逻辑自洽、语言流畅的报告。

在《论语言的通货膨胀》一文中作者提到了「语言通货膨胀」这个概念——“币制是交换财富的手段,语言是表达思想和情感的媒介;如同制币与其背后财富的不匹配而生的通胀,语言和语言背后的思想、情感的不匹配,就是语言的通胀。”若某个词语被随意、频繁地使用,该词语的词义将被弱化。此时,该词语所能表达的情感也将被弱化,导致情感表达的不到位,这便是语言的通货膨胀现象。

譬如在以前,我们会用「(笑)」或者「哈哈」来表达快乐的情绪。但现在的网络交流中,「哈」越多,貌似表达的情感越激烈,与之对照来看「哈哈」则显得过于“敷衍”。于是,即便想发「哈哈」也会多凑一些「哈」,避免对方觉得敷衍。这就说语言通货膨胀,在社交网站中,通货膨胀与表达能力的弱化无可避免。

通胀的背后是语言的贫瘠,表达能力的损伤,也是情感的缺失

其次,专注力被剥夺

社交网络会让我们的注意力变得支离破碎,专注力被剥夺。社交网络无时无刻不在产出消息,若自制力不足,则无时无刻不想着接收这些消息。并且现在获取信息的方式简直太多了,很多消息都是被动输入的,哪怕你没有看过《狂飙》,也会清楚地知道主演是谁、有什么梗、未删减的片段是怎么样的;哪怕你不认识某个演员,如果他发生了什么事情,大概率也会从社交网络中接收到。但这些信息有什么意义吗?于我又有何价值?

社交媒体大行其道的一个原因,就是它打破了努力创作有实际价值的作品和吸引到人们注意力之间的正相关关系。相反的,它用浅薄的集体主义式交换取代了永恒的资本主义交换:如果你注意我说了什么,我就会注意你说了什么,不管这话语有无价值。——《深度工作》

而太多的信息造成了信息过载,大脑的容量几乎被这些不断更新的无意义信息填满,如此便造成了消耗。除此之外,当接收的社会消息过多,对事物的敏感度也会降低,情绪变得麻木,不断渴望吃更大的瓜来满足自己在社交网络中的猎奇欲望。

最后,是个人价值被遗忘

互联网成为我们的精神居所,也成为我们与世界连接的媒介,我们处在一个“连接社会”,“人”与“物”之间的关系、乃至是人与人之间的“关系”,都被“连接”所取代。但是在这个网络连接中,我们每一个具体的、有丰富面向的人,在社交网络中只是一个抽象而单薄的收发的节点,个体的价值淹没于社交网络的汪洋中,鲜有人会静心体悟网络一隅中不知名陌生网友的感想,没有情感的透明流量大多被掠夺至宣发素材上。这样的网络,不值得逗留。

0x02 方法

我尝试了一些逃离的方法,选取几个有可行性的进行分享。

方法 1:信息从被动接受,转变成主动获取。

被动接受的大多是垃圾消息或是算法“精心”为你推荐的内容,所以核心一点是转被动为主动,用 RSS 订阅自己关注的周刊、博客、Email、网站、论坛、微博、公众号,每天 1-2 次定时去主动去扫描信息,只阅读你关注的内容。保持好自己的「输入节奏」。是我们在使用互联网,而不是被互联网「利用」。

除此之外需要去减少在「接触社交网络」上的消耗时间,比如关闭朋友圈入口、关闭微信的通知、把微信移除手机首屏。如果真的关心某个朋友,应该是主动点去朋友圈看,而非在票圈 Feed 流刷到了他的消息才去问候。而关闭微信通知推送也是同样的理由,不过找我的人较少,目前看来没有什么影响。

方法 2:培养邮件交流的习惯。

写邮件代替细碎聊天也是一个非常不错的方法,可以沉淀自己日常的想法并锻炼表达力,让自己的关注点回归文字本身,赋予语言与情感最真实、细腻的纹理。并且写信本身也是一件较为庄重的事,可以培养生活里的仪式感。

方法 3:每天为自己留点时间。

最后就是每天要自己留些时间(我是中午和晚上各半小时),这段时间不会有人打扰,也拒绝接受任何消息,专注于自己想做的事——阅读也好,工作也罢。这有利于培养自己专注力,做事时也能够更容易进入心流状态。

本月记录

刚刚过去的一月过得非常充实,把家里人接来深圳过了年,利用假期较为系统地学习了编译原理相关的知识以补充下内功,也借着工作之便实践了一下 babylon。沉下心来阅读,与友人写信,和家人凌晨去看《满江红》,翻出了很久没用的 PC 和精英版的 Xbox 手柄玩了一直想玩的《荒野大镖客 2》——应该是有史以来最棒的一月了。

  • 读完:哲学 |《悉达多》 | ★★★★★
  • 在读:小说 |《少年巴比伦》 | ★★★★★
  • 看完:电影 |《满江红》| ★★★★☆
  • 看完:动画 |《间谍过家家》 | ★★★★★
  • 在看:日剧 |《凪的新生活》 | ★★★★☆
  • 在看:剧集 |《三体》 | ★★★★★
  • 在看:动画 |《中国奇谭》 | ★★★★★
  • 在玩:Switch |《女神异闻录 P5R》| ★★★★★
  • 在玩:Steam |《荒野大镖客 2》| ★★★★★
  • 在玩:PS4 |《双人成行》| ★★★★☆

2022,平安喜乐

转眼间,这已经是第五篇年终总结了。从 2018 的「沉淀初心」到 2019 的「走走停停」,再到 2020 的「追逐星火」,与去年的「自渡向前」。至于今年的关键词,琢磨了许久,最后决定就叫「平安喜乐」吧!

平安喜乐,既是总结,但更多是期许

工作

每年的年终总结第一个环节都是工作,今年也不例外,毕竟是我投入精力最多的部分了。

今年工作有一个好的开端,年初的晋升答辩顺利通过,达成毕业两年半晋升 T10 的成就,不知这个耗时是否前无古人,但新的公司制度限制了晋升答辩的频次,所以应该是后无来者了。

去年的年终总结中,总结的第一个问题是「多线程事务处理能力薄弱」,如今再回顾,这个问题基本已经解决了。今年的日常中,几乎每天都有多项事情要处理。一个新事情来了,我不会立刻处理它,而是先记下来把它放进收件箱中。等我阶段性处理完手头上的事情之后,再去收集箱中给它打上优先级的标记,同时去看下收集箱里是否有 P0 标记的事项,如果有就立马切换过去处理,否则就处理 P1 或接着处理手上的事情。

简而言之,依靠收集箱与优先级就可以解决这个问题了:

  • 收集箱可以让自己不遗漏任何琐碎的事情,同时把其他事情临时放进收集箱也是一种处理,它不会打断自己手上正在做的事情。频繁切换线程是低效率最重要的因素,因此核心在于尽量避免频繁切换,保持专注。
  • 而优先级则方便高优的事情快速解决,不会耽误项目进程。

今年 Q3 有幸拿了公司的青年优才奖,同时绩效是 4+5,没有六连五星算是退步了吧(笑)。但认真说来,其实这个绩效是超乎了我的预期了,因为今年手头上没有负责过复杂的项目,自己在技术深度上的挖掘也是较为不满意的。如果此刻去答 T11,基本拿不出什么项目,因此感觉到今年在自我成长性上的欠缺。年底和 Leader 说了这个问题,同时也反思了一下,我个人在日常的项目中看问题的角度也是受限的。

手上的项目,无论难易与否、重要与否,其都有值得自己学习的东西。比如被我吐槽只是参与联调工作的项目 A,虽然是核心技术在公司 B 手上,但是我确实没有考虑过如果不依赖公司 B 的话,仅靠自己如何能实现同等或者是更好的项目效果。

《打开心智》里提到过一种“经验值心态”,把工作看成打怪联机。这种心态最大的价值在于,它为生活中大大小小的事情,都赋予了意义:

如果一项任务是重复、单调的:没关系,它能够提高我的熟练度,让我在这项技能的使用上更加得心应手。
如果遇到一个突如其来的问题:那更好,我触发了一项隐藏的挑战,又有一个新的目标可以去实现了……
如果遇到一个意料之中的问题:没关系,这本来就是我的主线任务,我辛辛苦苦练级就是为了通过它。
如果一项任务是艰巨、困难的:非常棒,它能够给我带来大量经验让我快速成长,获得更强的能力。

如果我可以想得足够多,并且不仅仅只满足于完成手上的工作,那成长性就不再是受限的了。本可以做的更好一些,这是今年的遗憾之一。

今年暑期带了一个很认真的实习生,看到了当年自己实习时的影子。那是 18 年,也是第一次写年终总结,标题是「沉淀初心」。最开始我学技术做产品的初心,如今还剩下多少。迷茫之际,时不时提醒着自己,现在所做的事情、所经历的一切、所收获的一切绝不是没有意义的,它们都会成为养分,在今后的路途上,会帮助自己更好地面对下一个关卡的“BOSS”。

今年业余期间学了 Chromium 和 JS 引擎的一些知识,沉淀了一些文章:

对技术的热情未曾消退,同时也发觉了自己有许多可以成长的空间。

2023,愿工作上不骄不躁,沉淀技术。

学习

这个模块打算总结一下自己今年在输入与输出上的两个改变。

第一个改变是发掘了新的输入方式。

今年开始听播客了,发现播客也是一个信息输入的好手段,但自己平时听得也并不多,只听《商业就是这样》和声动活泼的《跳进兔子洞》。

此外,发现自己获取信息的手段并不及时,且特别受推荐算法的影响陷入信息茧房之中。比如忘记打开平台的网站(如少数派、掘金这种),就会遗漏一些不错的文章;关注的技术公众号推来推去也总是那几篇文章在互相推。于是搭建了属于自己的 RSS 和邮件订阅系统(WJ.16: 个人信息流分享),采集信息和整理的效率高出了很多,是今年最满意的收获之一了。

第二个改变是开始写周刊了。

至于为什么要写周刊,在 WJ.1: 开刊,为什么写周刊 中详细说明过,这里就不赘述了。值得一提的是,周刊在中间停了两个月,因为写着写着意识到自己每周的(非技术)输入并不是很充足,且周末也没有足够的时间把它们整理成周刊,导致周刊的选题与行文的质量自己并不是很满意,于是在最后两个月中把它调整了月刊。

今后不管是否有读者,我都会为了未来的自己而坚持写下去的。

情绪

去年年终总结里提到的问题「容易陷入焦虑的负面情绪」,于是有个目标是「修炼自己的心性」,今年则重点做了情绪管理。下面是今年的心情记录,较以往几年而言,绿色的成分明显多了很多。

遇到某些事情的时候,情绪会突如其来,我会立马感觉到焦躁、愤怒或难过等负面情绪,但仅仅只有那么一瞬,我明白此刻是情绪占据了理智,任由自我情绪发泄是不当的行为。

情绪的本质是「大脑警报器」,而我把它当成了一个卫兵,当它告诉我此刻应该愤怒的时候,我确实立马感觉到了愤怒的情绪。但我又会告诉自己,情绪是不可控的,情绪的发泄此刻可能解决不了任何问题。我会告诉这个愤怒的卫兵:“我知道了,你退下吧。我接纳了这个情绪,现在咱们先冷静下来分析问题,回头再慢慢消化你的愤怒吧”。

而绝大多数时,当我处理完那件事或者处理不了接受现实之后,彼时的负面情绪也就烟消云散了,不存在什么回头慢慢消化。

我并非排斥那时的愤怒或悲伤,只是接纳了它。

除此之外,再分享一个我特别喜欢的方法:“每当我遇到自己不敢直视的困难时,我就会闭上双眼,想象自己是一个80岁的老人,为人生中曾放弃和逃避过的无数困难而懊悔不已,我会对自己说,能再年轻一次该有多好,然后我睁开眼睛:砰!我又年轻一次了!

此外在睡前回顾今日的心情时,也会选择性地去记录今天遇到的美好的事情,一方面是期望带来好梦,另一方面是期待明天可以更美好。因此今年心情记录的绿色偏多了不少,这是一个好的改变。

日记确实是需要如实的记录,但若只是选择性地忽略琐碎而烦心的小事,去记住更值得记录的美好,不是一件更美好的事吗?

感情

不堪回首的往事我不愿去想,但是今年春天这段短暂的恋情,即便现在回忆起来也只有美好的点滴。现如今,真的只剩下「一瞬の夢」了。

希望今后仍旧能抱着那时被珍视的心情,勇敢地继续走下去。

生活

今年搬了新家,换了居住三年的小屋子搬到了一个大屋子。

喜欢我房间窗外的晚霞。

喜欢上班路上的那条小河。

今年开始「计划消费」了,之前其实也记账,而且记了 6 年有余,但之前只是机械地记录而已,对花销没有什么控制,每个月底一看账单汇总总是想不起来为什么数字会是这样。今年换了《MoneyThings》来记录,制定了每周与每月的消费计划,每周还有进度条来可视化控制余额,算是非常好用了。

今年还购入了小车,是看剧种草的 mini F57。原本我四月订的是特斯拉,当时恋爱期间有车方便去广州,但订车有点盲目跟订的感觉,而且只是对特斯拉的品牌有认知,所以下定了,认为随众不会有错,所以后来也一直没有取消订单。

但是在剧里看到 mini 敞篷之后瞬间心动,简直就是梦中情车,当晚就了解了这个品牌的历史和车型,第二天预约试驾,考虑了一中午之后下午就给了定金。不知道这算不算冲动消费,但是现在用来一点儿也不后悔。很有个性、帅萌帅萌的一台小车,在大街上从来没有撞见到同款~

By the way,家里有很多只猫猫,手套和泡芙过得很好。

By the way,目前还没有阳,希望能坚持到年后。

圆梦

今年发生了很多事情,若排除掉社会面上负面的新闻,如果要我总结今年的关键词,我给出「圆梦」这个词。

首先是世界杯梅西圆梦,从19岁到35岁,人生有多少个16年能让你追随梦想。

然后是英雄联盟的戴先生的最后一舞,10 年来一直在追逐冠军,从最不被看好的四号种子到打败大魔王拿下冠军,这是一位电竞选手最完美的谢幕。

还有宝可梦的小智,从高级球之后我每周五晚上都在追《旅途》,真新镇小智的 25 年,从 1999 年石英联盟 16 强到 2002 城都联盟 8 强,2005 丰缘联盟 8 强,2010 神奥联盟 4 强,2013 合众联盟 8 强,再到最可惜的 2016 卡洛斯联盟亚军,最终 2019 拿下阿罗拉冠军,接着今年拿到世界锦标赛总冠军。

25 年冠军圆梦之路,虽然 25 年被编剧不断剧情杀,但是小智在观众心目中已经是当之无愧的宝可梦大师了!小智的故事最终还是谢幕了,希望最后的《特别篇》中能给他和皮卡丘一个好的结局。

PS. 其实打丹帝的时候更希望老宠回来,世界第一喷火龙的约定,还有和忍蛙一起登上巅峰的预言,这些观众都没有看到呀!25 年的结局真的不想留下一丁点儿遗憾。

最后还有灌篮高中全国大赛的动画化,再次看到湘北的比赛,圆了小时候的梦。

圆梦之年,祝愿大家也可以圆青春时的梦。

书音影

如果要评选今年买的最有价值的物品,那必然是投影仪了!今年周末看了很多剧。

简单列一下今年看过的剧、电影和书籍吧,大部分我会顺手给出评价或是吐槽一句。

剧集

  • 钱断情始》:★★★★★,喜欢春马的笑容,喜欢女主的反消费主义。
  • 行骗天下》:★★★★★,看完了剧集与四部剧场版,全部都给了 5 星好评。有笑点、燃点、泪点,剧本满分,长泽雅美满分!
  • 卖房子的女人》:★★★★☆,很热血的一部剧,没有干劲的时候可以看看。
  • 僵尸校园》:★★★★☆,我对丧尸片毫无抵抗力,这部丧尸片每集都堪比釜山行,虽高开低走,但也值得一看。
  • 非常律师禹英禑》:★★★★★,很温暖治愈的一部剧,看着莫名心情变好。By the way,mini F57 我就是在这部剧种草的,看着郑明锡在敞篷里吹风看风景,简直就是梦中情车,第二天就预约了试驾并下定了。
  • 金宵大厦》:★★★☆☆,吃饭的时候看的,互相魂穿的设定不错,但演技浮夸、情节平庸。
  • 财阀家的小儿子》:★★★☆☆,烂尾了。

电影

  • 狼少年》:★★★★☆
  • 独行月球》:★★★★☆
  • 坠落》:★★★☆☆
  • 头文字D》:★★★★★
  • 爱在黎明破晓前》:★★★★★
  • 爱在日落黄昏时》:★★★★★
  • 误杀瞒天记》:★★★★★
  • 最佳出价》:★★★★★,结局很震撼,千万不能开弹幕看!
  • 土拨鼠之日》:★★★★★,不愧为经典之作。
  • 密室逃生2》:★★★☆☆,商业爆米花电影。
  • 杀人回忆》:★★★★★,韩国电影的水平确实很高。
  • 奇异博士2》:★★★★☆,只看预告、搭配女巫&博士两个魔法大 IP,原本或许可以成为让第四阶段翻身的电影,但最终呈现的效果和主题深度上都稍显逊色。旺达的性格转变也太突兀了,让人怀疑导演甚至没有看过《旺达幻视》。
  • 这个杀手不太冷静》:★★★★☆,看名字以为是烂片,但有点出乎意料了。
  • 瞬息全宇宙》:★★★★★,丰富的想象力搭配光怪陆离地表现力,满分作品。
  • 花束般的恋爱》:★★★★★,令人难过的电影,但也同时是令人释怀的电影。
  • 熔炉》:★★★★★
  • 蜘蛛侠:英雄无归》:★★★★★,看在三蛛同屏的面子下给的情怀分。
  • 长津湖之水门桥》:★★★★☆,春节时和家人一起在影院看的。
  • 一个叫欧维的男人决定去死》:★★★★★,贼喜欢这种表现生活小日常的温情片。

动漫

  • 赛博朋克:边缘行者》:★★★★★
  • 红辣椒》:★★★★★,满分。
  • 未麻的部屋》:★★★★★,满分,但是大晚上看的,还有点害怕。
  • 《夏日重现》:★★★★★,老实说动画的节奏有点怪异,结尾的配乐也不好,没有漫画好看。
  • 间谍过家家》:★★★★★,看着很治愈。
  • 鬼灭之刃 游郭篇》:★★★★★,漫画党,在剧情保障的情况下还有优异的作画,着实是满分作品。
  • 《宝可梦 旅途》:★★★★☆(只看八大师之后)

阅读

  • 打开心智》:★★★★★,值得一读,可以学到很多东西。
  • 假面山庄》:★★★★☆
  • 记忆旅行者》:★★★★☆,设定很棒,画卷展开的节奏很吸引人,但结局有些仓促。
  • 谁杀了她》:★★★★☆
  • 沉默的巡游》:★★★★☆
  • 见字如面》:★★★★☆,前半部分 4 星,后半部分 2 星。
  • 红手指》:★★★★★
  • 占星术杀人魔法》:★★★★★,本格推理,读得很慢也没有读懂,搭配着视频分析才明白手法。
  • 桶川跟踪狂杀人事件》:★★★★★,纪实文,作者是一个记者,工作兢兢业业,心中一直有着名为正义的尺子不曾动摇,值得尊重和学习。
  • 恶意》:★★★★★,叙述性诡计玩得不错。
  • 祈祷落幕时》:★★★★☆
  • 悖论13》:★★★★☆
  • 挽救计划》:★★★★☆
  • 《苏东坡传》:★★★★★,“我一生之至乐在执笔为文之时,心中错综复杂之情思,我笔皆可畅达之。我自谓人生之乐,未有过于此者也。”

游戏

游戏就不评分啦,既然是选择玩通关了,本身就说明对我个人就一定的吸引力了。

  • 《三角战略》:战棋爱好者的盛宴,又是像素风格,简直太棒了!
  • 《异度之刃 3》:玩了几个小时,发现太肝了,弃了。
  • 《宝可梦剑盾 · 盾》:已通关,剧情太差,道馆太简单。联机部分还没有游玩。
  • 《十三机兵防卫圈》:游玩中,剧情进度有点慢,故事还没有展开,暂不评价。
  • 《女神异闻录 P5R》:不愧是天下第一的游戏,大约每两周推一个宫殿,目前还在赌场。
  • 《原神 3.0》:草神赛高!
  • 《王者荣耀》:以前很少玩,一直是白金钻石这样。前几个月开始每天下班来一局,居然一波单排连胜上了无双。

新年目标

最后是新年目标环节:

  1. 生活上:培养感恩心,多与人打交道,主动建立人与人之间的链接。(今年几乎没有认识新的朋友)
  2. 学习上:保持好奇心,继续保持好奇地学习心态,保持好奇,不断地好奇;追求真知,仅为求知而求知。
  3. 工作上:保持谦卑心,在完成项目的基础上,不要设置自己的边界。多去想,多思考,多实践,多反思,发掘并扩大影响力。
  4. 心态上:保持喜乐心,要擅于发现与记录身边的美好,过诗意的人生。

最后想用《钱断情始》的一句话作为 2022 的结语——“细流涓涓不绝,其水滴滴各异,泡沫浮于淤泥,且消乎且结乎,概无久存之例,世间人事,鸟兽栖息,亦不如此。

愿你,愿我,愿那些在空中飘荡的心愿,最终都能传达到温柔的彼岸。

月刊(第17期):心之所向,素履以往

心之所向,素履以往

最近迷上了「徐云流浪中国」的视频,up 主大约 2 日一更,记录自己骑行中国、风餐露宿的流浪生活。

徐云出身农村,两年没有工作过,只带着一辆单车、一个帐篷、一个睡袋、一个防尘布、一张防潮垫、一个折叠式柴火炉、一个打火机、一个高压锅、还有充电宝/手机/运动相机,骑行到了海拔 5000 米川西高原、零下 30 度东北边境。

每天在野外骑行,尽量避开人群,浏览自然风格。夜晚则孤身一人在桥底、道路旁、山沟里、废弃屋内搭帐篷过夜(废弃屋对于他而言简直就是五星级住房了)。并且几乎每顿晚饭都是自己捡木柴生火做饭,除了必要的补充补给之外很少进城住宾馆。

每期视频有近半的时长是做饭的时候对着镜头闲聊。对他而言,这种异步的沟通记录是种奇妙的体验;对我们而言,他成了我们看世界的眼睛。

我把视频推荐给朋友的时候,他们很难理解为什么会有人选择过这种生活。我看了网上的评价,除了鼓励之外,有质疑、有同情也有劝诫。

某期视频中徐云给出了回答:“但路就在那,你得往前走。

这让我想到了刘慈欣的《山》,小说中的主角执迷于登山,他说:“我们之所以登山,因为山就在那里。

小说中的地核文明也和人类的探险史如出一辙,一代又一代去探索外面的世界。无论是好奇心也好,是征服欲也罢,因为它就在那里,所以他们要去。基于一代又一代探险者的前赴后继,地核文明的加加林终于摆脱了暗无天日的地核,看到了灿烂的星空。

心之所向,犹梦何妨?

高中那会儿读《明朝那些事儿》,令我印象深刻的除了王阳明、海瑞、张居正、于谦等当年明月花了较长篇幅介绍的名人之外,还有一个人令我为之动容——徐霞客。在明朝七部曲的文末,当年明月用徐霞客的故事做了全篇的收尾,写得特别好,这里建议读者有空重温下。

当年明月用如此之多的篇幅,讲述一个王朝的兴起和衰落,在终结的时候,却说了徐霞客游记的故事。为什么呢?

此前,我讲过很多东西,很多兴衰起落、很多王侯将相、很多无奈更替,很多风云变幻,但这件东西,我个人认为,是最重要的。

因为我要告诉你,所谓千秋霸业,万古流芳,以及一切的一切,只是粪土。先变成粪,再变成土。

现在你不明白,将来你会明白,将来不明白,就再等将来,如果一辈子都不明白,也行。

而最后讲述的这件东西,它超越上述的一切,至少在我看来。

但这件东西,我想了很久,也无法用准确的语言,或是词句来表达,用最欠揍的话说,是只可意会,不可言传。

然而我终究是不欠揍的,在遍阅群书,却无从开口之后,我终于从一本不起眼,且无甚价值的读物上,找到了这句适合的话。

这是一本台历,一本放在我面前,不知过了多久,却从未翻过,早已过期的台历。

我知道,是上天把这本台历放在了我的桌前,它看着几年来我每天的努力,始终的坚持,它静静地,耐心地等待着终结。

它等待着,在即将结束的那一天,我将翻开这本陪伴我始终,却始终未曾翻开的台历,在上面,有着最后的答案。

我翻开了它,在这本台历上,写着一句连名人是谁都没说明白的名人名言。

是的,这就是我想说的,这就是我想通过徐霞客所表达的,足以藐视所有王侯将相,最完美的结束语:

成功只有一个——按照自己的方式,去度过人生。

《徐霞客游记》中有言:“初四日,兀坐听雪溜竟日。”

反思一下自己这月初四在做些什么,是奔忙着追逐富贵与功名、还是操劳着家庭、忧心着事业?但徐霞客却坐在黄山绝顶,听了一整天的大雪融化声。

徐霞客不是奔着写《徐霞客游记》去旅游的,辛弃疾不是奔着写《稼轩诗集》去救国的。

我们需要的是爱生活,而不是爱生活的意义。如同王小波所说:“我来这个世界,不是为了繁衍后代。而是来看花怎么开,水怎么流。太阳怎么升起,夕阳何时落下。我活在世上,无非想要明白些道理,遇见些有趣的事。生命是一场偶然,我在其中寻找因果。 ”

有首我很喜欢听的歌,叫《路过世界的旅行家》,歌词描述了旅行者的见闻与心境,最后以「那便是你,生而为人曾经存在的印迹」一句词作为结尾。人生是没有意义的,有的只是「一连串的刹那」,那便是我们存在的痕迹。

这一生最重要的是体验和感受,去拥抱美好,度过自己想要的人生。

最后以我崇拜的一位皮克斯设计师在采访中说过的话作为结尾——「成就不在于外在定义,全看是否按自己的意愿在活。

浮游于世,魂魄荧荧。

心之所向,素履以往。

本月记录

  • 看完:韩剧 |《财阀家的小儿子》| ★★★☆☆
  • 在看:日剧 |《凪的新生活》 | ★★★★☆
  • 在看:动画 |《三体》 | ★★☆☆☆
  • 在玩:Switch |《女神异闻录 P5R》| ★★★★★

《财阀》标准爽文穿越剧情,高开低走(不知道算不算高开),综合 3 分吧。如果可以把男女主换下演员可能效果会更好,全剧的演技基本靠爷爷撑起来的 Orz。最后期待一波今晚的大结局。

《凪的新生活》还没看完,并不是很喜欢这种强行洗白的剧情,编剧企图将对他人已然造成的伤害,归因成自身的性格缺陷。不过挺喜欢日剧这种小清新的生活气息。

《三体》动画一言难尽,看完第四集决定弃了,缺点很多就不吐槽了,提一嘴优点——广告营销做的很好。

月刊(第16期):个人信息流分享

周刊暂停了一段时间,发现每周保持更新对我的压力还是有些大的。经过调整之后,发觉 3 周更一下挺合适的,那 3 周更一下,不如就月更啦~所以这次月刊主要分享下 11 月发现的好玩意儿!

个人信息流分享

本期分享一下我的信息流,简单画了个图示:

这套信息流个人用起来非常舒服,只要在 inoreader 或 reader 上进行文章阅读即可,搭配 Readwise 服务,阅读文章时的标注可以自动同步到 Logseq 等笔记软件上。

第一步:输入,即订阅的信息源。

这里我订阅了一些公众号的推送、B 站 Up 主的更新通知,以及 V2EX、少数派、掘金、豆瓣小组、知乎热榜、微博热搜等 RSS;如果是订阅的 newsletter,还可以配置邮件转发,将所有需要的数据统一输入到 inoreader 上。

第二步:采集,即通过自动化初筛信息源。

这里使用 inoreader 的规则和监控能力,可以监控新增信息源的关键词。比如我监控了 Vite,之后搭了个 Webhook 转发服务,当掘金、知乎、前端 Q 公众号推送了关于 Vite 的文章之后,inoreader 会触发我自定义的 Webhook,将文章推送到我的企业微信机器人上,以此达到信息源监控的效果。

同理,我也监控《深圳卫健委》公众号的新增文章,若标题有「日新增」字样也会发送到我的企业微信机器人上,从而实现订阅深圳疫情实况的功能。

第三步:阅读,即阅读文章并进行标注笔记。

这里使用 Reeder 或者直接在 inoreader 上阅读均可,前者是原生开发的阅读器,体验会更好一些,无论是 Reeder 还是 inoreader 都支持同样的类 vim 快捷键,阅读体验非常优秀:

  • J:上一篇
  • K:下一篇
  • M:mark unread
  • S:mark star
  • 空格:翻页

搭配这套快捷键,每天大概可以消化 300 篇+的文章,其中会挑十几篇 mark star 之后精读。

标注也很简单,阅读时直接划线即可,无论是在阅读器里划线还是笔记,都可以自动推送到 Readwise 上。

第四步:整理输出,即使用 Logseq 等笔记软件整理阅读笔记。

Logseq 和 Obsidian 都有 Readwise 官方插件,可以定期同步标注笔记到本地笔记中,之后进行整理加工。

Logseq 直接输出标注的效果演示:

Obsidian 直接输出标注的效果演示(整合 DataView):

本期记录

  • 看完:日剧 |《钱断情始》| ★★★★★
  • 看完:日剧 |《行骗天下:英雄篇》| ★★★★★
  • 看完:动画 |《名侦探柯南:万圣节的新娘)》| ★★★☆☆
  • 在看:动画 |《间谍过家家》| ★★★★★
  • 在看:动画 |《万神殿》| ★★★★☆
  • 在看:日剧 |《世界奇妙物语》| ★★★★☆
  • 在玩:Switch |《女神异闻录 P5R》| ★★★★★

《行骗天下》强烈安利,英雄篇抽空还二刷了一下,明明知道一定会有反转,但反转来临的时候还是会被燃到(PS. 很喜欢达子这个人设)

而看《世界奇妙物语》的感觉就和开盲盒一样,不知道这个短篇是有意思还是无趣的,综合之下还是给了 4 星。

至于《钱断情始》明明是一个喜剧片,但第四集还是哭到我。终于理解为什么那么多人喜欢春马了,春马的笑容着实治愈,把角色的快乐和阳光演绎到观众内心深处。渐渐接受了以后的人生里,遇到的告别会多过相遇,遇到的失去会多过得到。

——「细流涓涓不绝,其水滴滴各异,泡沫浮于淤泥,且消乎且结乎,概无久存之例,世间人事,鸟兽栖息,亦不如此。」

除此之外,也很喜欢女主的反消费主义,购物前需要想想它是消费、浪费还是投资。毕竟钱是拿生命能量换来的,如果购买的东西配不上自己的生命能量,那比起物质消费,应该要做到更爱自己。

Chromium 渲染流水线——字节码到像素的一生

现代浏览器架构

在开始介绍渲染流水线之前,我们需要先介绍一下 Chromium 的浏览器架构与 Chromium 的进程模型作为前置知识。

两个公式

公式 1: 浏览器 = 浏览器内核 + 服务

  • Safari = WebKit + 其他组件、库、服务
  • Chrome = Chromium + Google 服务集成
  • Microsoft Edge (Chromium) = Chromium + Microsoft 服务集成
  • Yandex Browser = Chromium + Yandex 服务集成
  • 360 安全浏览器 = Trident + Chromium + 360 服务集成
  • Chromium = Blink + V8 + 其他组件、库、服务

公式 2:内核 = 渲染引擎 + JavaScript 引擎 + 其他

Browser Rendering Engine JavaScript Engine
Internet Explorer Trident (MSHTML) JScript/Chakra
Microsoft Edge EdgeHTML → Blink Chakra → V8
Firefox Gecko SpiderMonkey
Safari KHTML → WebKit JavaScriptCore
Chrome WebKit → Blink V8
Opera Presto → WebKit → Blink Carakan → V8

这里我们可以发现除了 Firefox 和已经死去的 IE,市面上大部分浏览器都朝着 Blink + V8 或是 WebKit + JavaScriptCore 的路线进行演变。

渲染引擎

负责解析 HTML, CSS, JavaScript,渲染页面。

以 Firexfox 举例,有以下工作组:

  • Document parser (handles HTML and XML)
  • Layout engine with content model
  • Style system (handles CSS, etc.)
  • JavaScript runtime (SpiderMonkey)
  • Image library
  • Networking library (Necko)
  • Platform-specific graphics rendering and widget sets for Win32, X, and Mac
  • User preferences library
  • Mozilla Plug-in API (NPAPI) to support the Navigator plug-in interface
  • Open Java Interface (OJI), with Sun Java 1.2 JVM
  • RDF back end
  • Font library
  • Security library (NSS)

接下来,我们看看 WebKit 的发展历程。

Apple 2001 年基于 KHTML 开发了 WebKit 作为 Safari 的内核,之后 Google 在 2008 年时基于 WebKit 自研 Chromium,那时候的 Chrome 渲染引擎采用的也是 Webkit。2010 年时,Apple 升级重构了 WebKit,其就是如今 WKWebView 与 Safari 的渲染引擎 WebKit2。2013 年时,Google 基于 WebKit 开发了自己的渲染引擎—— Blink,其作为如今 Chromium 的渲染引擎。因为开源协议的关系,我们如今看 Blink 源码依然能看到很多 Apple 和 WebKit 的影子。

WebKit 的演变路线大致历程如下图所示:

通过 Web Platform Tests 的测试报告可见 Chromium 渲染引擎的兼容性也是极好的:

JavaScript 引擎

JavaScript 引擎在浏览器中通常作为渲染引擎内置的一个模块,但同时它的独立性非常好,也可以作为独立的引擎移植到其他地方使用。

这里列举几个业内有名的 JavaScript 引擎:

  • SpiderMonkey: Mozilla 的 JavaScript 引擎,使用 C/C++ 编写,作为 Firefox 的 JavaScript 引擎。
  • Rhino: Mozilla 的开源 JavaScript 引擎,使用 Java 编写。
  • Nashorn: Oracle Java Development Kit (JDK) 8 开始内置的 JavaScript 引擎,使用 Java 编写。
  • JavaScriptCore: WebKit 内置的 JavaScript 引擎,其作为系统提供给开发者使用,iOS 移动端应用可以直接零增量引入 JavaScriptCore(但这种场景下无法开启 JIT)。
  • ChakraCore: Microsoft 的开源 JavaScript 引擎,而如今已全面使用 Chromium 作为 Edge,因此除了 Edge iOS 移动端以外(Chromium iOS 端使用 JavaScriptCore 作为 JavaScript 引擎),其他端的 Edge 使用的都是 V8 引擎。
  • V8: Google 的开源 JavaScript 引擎,使用 C++ 编写,作为 Chromium(或者更进一步可以说 Blink)的内置 JavaScript 引擎,同时也是 Android 系统 WebView 的内置引擎(因为 Android WebView 也是 Chromium 嘛,笑)。性能优异,开启 JIT 之后的性能吊打一众引擎。此外,ES 语法兼容性表现也比较优秀(可见后文表格)。
  • JerryScript: Samsung 开源的 JavaScript 引擎,被 IoT.js 使用。
  • Hermes: Facebook 的开源 JavaScript 引擎,为 React Native 等 Hybrid UI 系统打造的引擎。支持直接加载字节码,从而使得 JS 加载时间缩短,让 TTI 得到优化。此外引擎还对字节码做过优化,且支持增量加载,对中低端机更友好。但是其设计为胶水语言解释器而存在,故不支持 JIT。(移动端 JS 引擎会限制 JIT 的使用,因为开 JIT 之后预热时间会变得很长,从而影响页面首屏时间;此外也会增加包体积和内存占用。)
  • QuickJS: 由 FFmpeg 作者 Fabrice Bellard 开发,体积极小(210 KB),且兼容性良好。直接生成字节码,且支持引入 C 原生模块,性能优异。在单核机器上有着 300 μs 极低的启动时间,内存占用也极低,使用引用计数,内存管理优秀。QuickJS 非常适用于 Hybrid 架构、游戏脚本系统或其他嵌入式系统。

各引擎性能表现如下图所示:

ECMAScript 标准支持情况:

Chromium 进程模型

Chromium 有 5 类进程:

  • Browser Process:1 个
  • Utility Process:1 个
  • Viz Process:1 个
  • Plugin Process:多个
  • Render Process:多个

抛开 Chrome 扩展的 Plugin Process,和渲染强相关的有 Browser Process、Render Process、Viz Process。接下来,我们重点看看这 3 类进程。

Render Process

  • 数量:多个
  • 职责:负责单个 Tab 内单个站点(注意跨站点 iframe 的情况)的渲染、动画、滚动、Input 事件等。
  • 线程:
  • Main thread x 1
  • Compositor thread x 1
  • Raster thread x 1
  • worker thread x N

Render Process 负责的区域是 WebContent:

Main thread

职责:

  • 执行 JavaScript
  • Event Loop
  • Document 生命周期
  • Hit-testing
  • 事件调度
  • HTML、CSS 等数据格式的解析

Compositor Thread

职责:

  • Input Handler & Hit Tester
  • Web Content 中的滚动与动画
  • 计算 Web Content 的最优分层
  • 协调图片解码、绘制、光栅化任务(helpers)

其中,Compositor thread helpers 的数目取决于 CPU 核心数。

Browser Process

  • 数量:1 个
  • 职责:负责 Browser UI (不包含 WebContent 的 UI)的全部能力,包括渲染、动画、路由、Input 事件等。
  • 线程:
  • Render & Compositing Thread
  • Render & Compositing Thread Helpers

Viz Process

  • 数量:1 个
  • 职责:接受 Render Process 和 Browser Process 产生的 viz::CompositorFrame,并将其合成 (Aggregate),最后使用 GPU 将合成结果上屏 (Display)。
  • 线程:
  • GPU main thread
  • Display Compositor Thread

Chromium 的进程模式

  • Process-per-site-instance:老版本的默认策略,如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点(根域名与协议相同)的话,那么这两个页面会共用一个 Render Process。
  • Process-per-site
  • Process-per-tab:如今版本的默认策略,每个 Tab 起一个 Render Process。但注意站点内部的跨站 iframe 也会启动一个新的 Render Process。可看下文 Example。
  • Single Process:单进程模式,启动参数可控,用于 Debug。

示例:

假设现在有 3 个 Tab,分别打开了 foo.com,bar.com,baz.com 三个站点,其中 bar.com、baz.com 不涉及 iframe;但 foo.com 涉及,它的代码如下所示:

<html>
<iframe id=one src="foo.com/other-url"></iframe>
<iframe id=two src="bar.com"></iframe>
</html>

那么按照 Process-per-tab 模式,最终的进程模型如下图所示:

Chromium 渲染流水线

至今前置知识已介绍完毕,开启本文的核心部分 —— Chromium Rendering Pipeline。

所谓渲染流水线,就是从接受网络的字节码开始,一步步处理这些字节码把它们转变成屏幕上像素的过程。经过梳理之后,包括以下 13 个流程:

  1. Parsing
  2. Style
  3. Layout
  4. Pre-paint
  5. Paint
  6. Commit
  7. Compositing
  8. Tiling
  9. Raster
  10. Activate
  11. Draw
  12. Aggregate
  13. Display

整理了一下各自流程所在的模块与进程线程,绘制的最终流水线如下图所示:

下文,我们一步步来看。

注:本文属于 Overview,所以力求简洁、不贴源码,但是会把设计到源码的部分打上源码链接,读者们可以自己索引阅读。同时,有些环节我撰写了更详细的流程分析文章,会贴在对应章节的开头处,感兴趣的读者可以点进去详细阅读。

Parsing

本节推荐阅读该系列的文章《Chromium Rendering Pipeline - Parsing》以深入了解 Parsing。

  • 模块:blink
  • 进程:Render Process
  • 线程:Main thread
  • 职责:解析 Browser Process 网络线程传过来的 bytes,经过解析处理,生成 DOM Tree
  • 输入:bytes
  • 输出:DOM Tree

这个环节设计的数据流为:bytes → characters → token → nodes → object model (DOM Tree)

我们把数据流的每次扭转进行梳理,得到以下 5 个环节:

  1. Loading:Blink 从网络线程接收 bytes
  2. Conversion: HTMLParser 将 bytes 转为 characters
  3. Tokenizing: 将 characters 转为 W3C 标准的 token
  4. Lexing: 通过词法分析将 token 转为 Element 对象
  5. DOM construction: 使用构建好的 Element 对象构建 DOM Tree

Loading

职责:Blink 从网络线程接收 bytes。

流程:

Conversion

职责:将 bytes 解析为 characters。

核心堆栈:

#0 0x00000002d2380488 in blink::HTMLDocumentParser::Append(WTF::String const&) at /Users/airing/Files/code/chromium/src/third_party/blink/renderer/core/html/parser/html_document_parser.cc:1037
#1 0x00000002cfec278c in blink::DecodedDataDocumentParser::UpdateDocument(WTF::String&) at /Users/airing/Files/code/chromium/src/third_party/blink/renderer/core/dom/decoded_data_document_parser.cc:98
#2 0x00000002cfec268c in blink::DecodedDataDocumentParser::AppendBytes(char const*, unsigned long) at /Users/airing/Files/code/chromium/src/third_party/blink/renderer/core/dom/decoded_data_document_parser.cc:71
#3 0x00000002d2382778 in blink::HTMLDocumentParser::AppendBytes(char const*, unsigned long) at /Users/airing/Files/code/chromium/src/third_party/blink/renderer/core/html/parser/html_document_parser.cc:1351

Tokenizing

职责:将 characters 解析为 token。

核心函数:

需要注意的是,这一步中如果解析到 link、script、img 标签时会继续发起网络请求;同时解析到 script 时,需要先执行完解析到的 JavaScript,才会继续往后解析 HTML。因为 JavaScript 可能会改变 DOM 树的结构(如 document.write() 等),所以需要先等待它执行完。

Lexing

职责:将 token 解析为 Element。

核心函数:

注意这一步在处理的过程中,就会使用栈结构存储 Node (HTML Tag),以便后续构造 DOM Tree —— 例如对于 HTMLToken::StartTag 类型的 Token,就会调用 ProcessStartTag 执行一个压栈操作,而对于HTMLToken::EndTag 类型的 Token,就会调用 ProcessEndTag 执行一个出栈操作。

如针对如下所示的 DOM Tree:

<div>
<p>
<div></div>
</p>
<span></span>
</div>

各 Node 压榨与出栈流程如下:

DOM construction

职责:将 Element 实例化为 DOM Tree。

最终 DOM Tree 的数据结构可以断点从 blink::TreeScope 中预览:

我们可以使用 DevTools 查看页面的 Parsing 流程:

但是这个火焰图看不到 C++ 侧的栈调用情况。如果想深入查看内核侧的堆栈情况, 可以使用 Perfetto 进行页面录制与分析,它不仅能看到 C++ 侧的堆栈情况,还能分析每个调用所属的线程,以及跨进程通信时也会连线标出发出通信与接收到通信的函数调用。

分析完 Paring 之后,我们可以完善一下我们的流程图:

Style

  • 模块:blink
  • 进程:Render Process
  • 线程:Main thread
  • 职责:Style Engine 遍历 DOM,通过匹配 CSSOM 进行样式分析 (resolution) 和样式重算 (recalc) 构建出 Render Tree
  • 输入:DOM Tree
  • 输出:Render Tree

RenderTree 由 RenderObject 构成,每个 RenderObject 对应一个 DOM 节点上,它会在 DOM 附加 ComputedStyle (计算样式)信息。

ComputedStyle 可以通过 DevTools 直接查看,CSS 调试时经常使用。

核心函数:Document::UpdateStyleAndLayout (可以先不看 Layout 的部分)

该函数的的逻辑如下图所示,这个生成 ComputedStyle 的环节我们称之为 style recalc(样式计算):

完整的 Style 的流程如下图所示:

我们可以拆成 3 个环节:

  1. CSS 加载
  2. CSS 解析
  3. CSS 计算

CSS 加载

核心堆栈的打印:

[DocumentLoader.cpp(558)] “<!DOCType html>\n<html>\n<head>\n<link rel=\”stylesheet\” href=\”demo.css\”> \n</head>\n<body>\n<div class=\”text\”>\n <p>hello, world</p>\n</div>\n</body>\n</html>\n”
[HTMLDocumentParser.cpp(765)] “tagName: html |type: DOCTYPE|attr: |text: “
[HTMLDocumentParser.cpp(765)] “tagName: |type: Character |attr: |text: \n”
[HTMLDocumentParser.cpp(765)] “tagName: html |type: startTag |attr: |text: “
…
[HTMLDocumentParser.cpp(765)] “tagName: html |type: EndTag |attr: |text: “
[HTMLDocumentParser.cpp(765)] “tagName: |type: EndOfFile|attr: |text: “
[Document.cpp(1231)] readystatechange to Interactive
[CSSParserImpl.cpp(217)] recieved and parsing stylesheet: “.text{\n font-size: 20px;\n}\n.text p{\n color: #505050;\n}\n”

需要注意的是 DOM 构建之后不会立刻渲染 HTML 页面,而是要等待 CSS 处理完毕。因为 CSS 加载完之后才会进行后续的 style recalc 等流程,如果没有 CSS 只渲染无样式的 DOM 是无意义的。

The browser blocks rendering until it has both the DOM and the CSSOM.  ——Render blocking CSS

CSS 解析

CSS 解析涉及的数据流为:bytes → characters → tokens → StyleRule → RuleMap,bytes 的处理前文已经说过,不再赘述,我们重点看后续的流程。

首先是:characters → tokens。

css 涉及到的 token 有下图这些:

需要注意的是 FunctionToken 会有额外的计算。例如,Blink 底层使用 RGBA32 来存储 Color (CSSColor::Create)。根据我微基准测试的结果,Hex 转换为 RGBA32 比 rgb() 的效率快 15% 左右。

第二步是:tokens → StyleRule。

StyleRules = selectors(选择器) + properties(属性集)。

值得注意的的是 CSS 选择器解析是从右向左

例如对于这个 CSS:

.text .hello{
color: rgb(200, 200, 200);
width: calc(100% - 20px);
}
#world{
margin: 20px;
}

解析结果如下所示:

selector text = “.text .hello”
value = “hello” matchType = “Class” relation = “Descendant”
tag history selector text = “.text”
value = “text” matchType = “Class” relation = “SubSelector”
selector text = “#world”
value = “world” matchType = “Id” relation = “SubSelector”

这里额外说一下 Blink 的默认样式,Blink 有一套应用默认样式的规则:加载顺序为 html.css (默认样式)→ quirk.css (怪异样式)→ android/linux/mac.css(各操作系统样式) → other.css(业务样式)。

更多内置 CSS 加载顺序可参考 blink_resources.grd 配置。

最后是:StyleRule → RuleMap。

所有的 StyleRule 会根据选择器类型存储在不同的 Map 中,这样做的目的是为了在比较的时候能够很快地取出匹配第一个选择器的所有 rule,然后每条 rule 再检查它的下一个 selector 是否匹配当前元素。

建议阅读: blink/renderer/core/css/rule_set.h

CSS 计算

  • 产物:ComputedStyle

为什么要计算 CSS Style?因为可能会有多个选择器的样式命中了 DOM 节点,还需要继承父元素的属性以及 UA 提供的属性。

步骤:

  1. 找到命中的选择器
  2. 设置样式

指的注意的是最后应用样式的优先级顺序:

  1. Cascade layers 顺序
  2. 选择器优先级顺序
  3. proximity 排序
  4. 声明位置顺序

我们都知道应用样式的优先级顺序是选择器优先级相加,但这只是里面的第二级优先级。如果前三个优先级完全相同的情况下,最后应用的样式会取决于样式的声明时机 —— 声明靠后的优先级越大。

如图:

这里的 h1 的 class,无论写成 main-heading 2 main-heading 还是调转顺序,标题都是蓝色的,因为 .main-heading2 的声明靠后,因此优先级更高。

Layout

  • 模块:blink
  • 进程:Render Process
  • 线程:Main thread
  • 职责:处理 Element 的几何属性,即位置与尺寸
  • 输入:Render Tree
  • 输出:Layout Tree

Layout Object 记录了 Render Object 的几何属性。

一个 LayoutObject 附加了一个 LayoutRect 属性,包括:

  • x
  • y
  • width
  • height

但需要注意的是,LayoutObject 与 DOM Node 并非 1:1 的关系,理由如下图所示:

Layout 流程的核心函数:Document::UpdateStyleAndLayout ,经过这一步之后 DOM tree 会变成 Layout Tree,如下图代码:

<div style="max-width: 100px">
  <div style="float: left; padding: 1ex">F</div>
  <br>The <b>quick brown</b> fox
  <div style="margin: -60px 0 0 80px">jumps</div>
</div>

每一个 LayoutObject 节点都记录了位置和尺寸信息: 我们知道避免 Layout (reflow),可以提高页面的性能。那么如何减少重排呢?主旨是合并多个 reflow,最后再反馈到 render tree 中。具体有以下措施:

  • 直接更改 classname 而非 style → 避免 CSSOM 重新生成与合成
  • 让频繁 reflow 的 Element “离线”
  • 替代会触发 reflow 的属性
  • 将 reflow 的影响范围控制在单独的图层内

其中,会首次/二次触发 Layout(reflow),Paint(repaint),Compositor 的属性可以参考 CSS Triggers

可以发现每个浏览器内核对于属性的处理是不一样的,如果需要优化性能,就可以对照查看这张表格,看看有没有 css 属性是可以优化的。

Pre-paint

  • 模块:blink
  • 进程:Render Process
  • 线程:Main thread
  • 职责:生成 Property trees,供 Compositor thrread 使用,避免某些资源重复 Raster
  • 输入:Layout Tree
  • 输出:Property Tree

基于属性树,Chromium 可以单独操作某个节点的变换、裁剪、特效、滚动,不至于影响它的子节点。

核心函数:

新版本 Chromium 改成了 CAP(composite after paint)模式

Property trees 包括以下四棵树:

Paint

  • 模块:blink
  • 进程:Render Process
  • 线程:Main thread
  • 职责:Blink 对接 cc 的绘制接口进行 Paint,生成 cc 模块的数据源 cc::Layer
  • 输入:Layout Object
  • 输出:PaintLayer (cc::Layer)

注意:cc = content collator (内容编排器),而不是 Chromium Compositor。

核心函数:

Paint 阶段将 Layout Tree 中的 Layout Object 转换成绘制指令,并把这些操作封装在 cc::DisplayItemList 中,之后将其注入进 cc::PictureLayer 中。

生成 display item list 的流程也是一个栈结构的遍历:

再举一个例子,针对以下 HTML:

<style> #p {
position: absolute; padding: 2px;
width: 50px; height: 20px;
left: 25px; top: 25px;
border: 4px solid purple;
background-color: lightgrey;
} </style>
<div id=p> pixels </div>

对应生成的 display items 如下图所示:

最后再介绍一下 cc::Layer,它运行在主线程,且一个 Render Process 内有且只有一棵 cc::Layer 树。

一个 cc::Layer 表示一个矩形区域内的 UI,以下子类代表不同类型的 UI 数据:

  • cc::PictureLayer:用于实现自绘型的 UI 组件,它允许外部通过实现 cc::ContentLayerClient 接口提供一个 cc::DisplayItemList 对象,它表示一个绘制操作的列表,记录了一系列的绘制操作。它经过 cc 的流水线之后转换为一个或多个 viz::TileDrawQuad 存储在 viz::CompositorFrame 中。
  • cc::TextureLayer:对应 viz 中的 viz::TextureDrawQuad,所有想要使用自己的逻辑进行 Raster 的 UI 组件都可以使用这种 Layer,比如 Flash 插件,WebGL等。
  • cc::UIResourceLayer/cc::NinePatchLayer:类似 TextureLayer,用于软件渲染。
  • cc::SurfaceLayer/cc::VideoLayer(废弃):对应 viz 中的 viz::SurfaceDrawQuad,用于嵌入其他的 CompositorFrame。Blink 中的 iframe 和视频播放器可以使用这种 Layer 实现。
  • cc::SolidColorLayer:用于显示纯色的 UI 组件。

Commit

  • 模块:cc
  • 进程:Render Process
  • 线程:Compositor thread
  • 职责:将 Paint 阶段的产物数据 (cc::Layer) 提交给 Compositor 线程
  • 输入:cc::Layer (main thread)
  • 输出:LayerImpl (compositor thread)

核心函数:PushPropertiesTo

核心逻辑是将 LayerTreeHost 的数据 commit 到 LayerTreeHostImpl,我们在接收到 Commit 消息的地方进行断点,堆栈如下所示:

libcc.so!cc::PictureLayer::PushPropertiesTo(cc::PictureLayer * this, cc::PictureLayerImpl * base_layer)
libcc.so!cc::PushLayerPropertiesInternal<std::__Cr::__wrap_iter<cc::Layer**> >(std::__Cr::__wrap_iter<cc::Layer**> source_layers_begin, std::__Cr::__wrap_iter<cc::Layer**> source_layers_end, cc::LayerTreeHost * host_tree, cc::LayerTreeImpl * target_impl_tree)
libcc.so!cc::TreeSynchronizer::PushLayerProperties(cc::LayerTreeHost * host_tree, cc::LayerTreeImpl * impl_tree)
libcc.so!cc::LayerTreeHost::FinishCommitOnImplThread(cc::LayerTreeHost * this, cc::LayerTreeHostImpl * host_impl)
libcc.so!cc::SingleThreadProxy::DoCommit(cc::SingleThreadProxy * this)libcc.so!cc::SingleThreadProxy::ScheduledActionCommit(cc::SingleThreadProxy * this)libcc.so!cc::Scheduler::ProcessScheduledActions(cc::Scheduler * this)
libcc.so!cc::Scheduler::NotifyReadyToCommit(cc::Scheduler * this, std::__Cr::unique_ptr<cc::BeginMainFrameMetrics, std::__Cr::default_delete<cc::BeginMainFrameMetrics> > details)
libcc.so!cc::SingleThreadProxy::DoPainting
libcc.so!cc::SingleThreadProxy::BeginMainFrame(cc::SingleThreadProxy * this, const viz::BeginFrameArgs & begin_frame_args)

Compositing

  • 模块:cc
  • 进程:Render Process
  • 线程:Compositor thread
  • 职责:将整个页面按照一定规则,分成多个独立的图层,便于隔离更新
  • 输入:PaintLayer(cc::Layer)
  • 输出:GraphicsLayer

核心函数:

为什么需要 Compositor 线程?那我们假设下如果没有这个步骤,Paint 之后直接光栅化上屏又会怎样:

如果直接走光栅化上屏,如果 Raster 所需要的数据源因为各种原因,在垂直同步信号来临时没有准备就绪,那么就会导致丢帧,发生 “Janky”。

当然,为了避免 Janky,Chromium 也在每个阶段也做了很常规的优化——缓存。如下图所示,在 Style、Layout、Paint、Raster 阶段都做了对应了缓存策略,以避免不必要的渲染,从而减少 Janky 发生的可能性:

但即便做了如此多的缓存优化,一个简单的滚动会导致所有的像素重新 Paint + Raster!

而 Compositing 阶段经过分层之后的产物 GraphicsLayer,可以让 Chromium 在渲染时只需要操作必要的图层,其他图层只需要参与合成就行了,以此提高渲染效率:

如下图所示: wobble 类有个 transform 动画,那么这整个 div 节点就是一个独立的 GraphicsLayer,动画只需要渲染这部分 layer 即可。

我们也可以通过 DevTools 的图层工具查看所有的 Layers,它会告诉我们这个图层产生的原因是什么、内存占用多少,至今为止绘制了多少次,以便我们进行内存与渲染效率的优化。

这也解答了为什么 CSS 动画性能表现优秀?因为有 Compositor 线程的参与,它基于 Property Trees 合成的图层,单独在 Compositor 线程处理 CSS 动画。此外,我们也可以使用 will-change 去提前告知 Compositor 线程,以优化图层合并。但这个方案也不是万能的,每个 Layer 都会消耗一定的内存。

Compositor Thread 还具备处理输入事件的能力,如下图所示,它会监听从 Browser Process 过来的各种事件:

但需要注意的是如果在 JavaScript 注册了事件监听,它会把输入事件转发给 main thread 进行处理。

Tiling

  • 模块:cc
  • 进程:Render Process
  • 线程:Compositor thread
  • 职责:将一个 cc::PictureLayerImpl 根据不同的 scale 级别,不同的大小拆分为多个 cc::TileTask 任务给到 Raster 线程处理。
  • 输入:LayerImpl (compositor thread)
  • 输出:cc::TileTask (raster thread)

图块(Tiling)是 Raster 的基本工作单位,这个阶段中 Layer (LayerImpl) 会拆成一个个 Tiling。在 Commit 完成之后会根据需要创建 Tiles 任务 cc::RasterTaskImpl,这些任务被 Post 到 Raster 线程中执行。

核心函数:PrepareTiles

推荐阅读:cc/tiles/tile_manager.h

这个环节主要是提交 cc::TileTask 任务给到 raster thread 做分块渲染 (Tile Rendering),所谓分块渲染就是把网页的缓存分为一格一格的小块,通常为 256x256 或者 512x512,然后分块进行渲染。

分块渲染的必要性提现在以下两个方面:

  • GPU 合成通常是使用 OpenGL ES 贴图实现的,这时候的缓存实际就是纹理(GL Texture),很多 GPU 对纹理的大小是有限制的。GPU 无法支持任意大小的缓存。
  • 分块缓存,方便浏览器使用统一的缓冲池来管理缓存。缓冲池的小块缓存由所有 WebView 共用,打开网页的时候向缓冲池申请小块缓存,关闭网页是这些缓存被回收。

如果说前一个环境的分层是宏观上提升了渲染效率,那么分块就是微观上提升了渲染效率。

Chromium 对分块渲染的策略还有以下优化点:

  1. 优先绘制靠近视口的图块:Raster 会根据 Tiling 与可见视口的距离安排优先顺序进行 Raster,离得近的会被优先 Raster,离得远的会降级 Raster 的优先级。
  2. 在首次合成图块的时候,降低分辨率,以减少纹理合成和上传的耗时。

在提交 TileTask 的位置我们断点,可以看到该环节的完整堆栈:

libcc.so!cc::SingleThreadTaskGraphRunner::ScheduleTasks(cc::TestTaskGraphRunner * this, cc::NamespaceToken token, cc::TaskGraph * graph)
libcc.so!cc::TileTaskManagerImpl::ScheduleTasks(cc::TileTaskManagerImpl * this, cc::TaskGraph * graph)
libcc.so!cc::TileManager::ScheduleTasks(cc::TileManager * this, cc::TileManager::PrioritizedWorkToSchedule work_to_schedule)
libcc.so!cc::TileManager::PrepareTiles(cc::TileManager * this, const cc::GlobalStateThatImpactsTilePriority & state)
libcc.so!cc::LayerTreeHostImpl::PrepareTiles(cc::LayerTreeHostImpl * this)
libcc.so!cc::LayerTreeHostImpl::NotifyPendingTreeFullyPainted(cc::LayerTreeHostImpl * this)libcc.so!cc::LayerTreeHostImpl::UpdateSyncTreeAfterCommitOrImplSideInvalidation(cc::LayerTreeHostImpl * this)
libcc.so!cc::LayerTreeHostImpl::CommitComplete(cc::LayerTreeHostImpl * this)
libcc.so!cc::SingleThreadProxy::DoCommit(cc::SingleThreadProxy * this)libcc.so!cc::SingleThreadProxy::ScheduledActionCommit(cc::SingleThreadProxy * this)
libcc.so!cc::Scheduler::ProcessScheduledActions(cc::Scheduler * this)
libcc.so!cc::Scheduler::NotifyReadyToCommit(cc::Scheduler * this, std::__Cr::unique_ptr<cc::BeginMainFrameMetrics, std::__Cr::default_delete<cc::BeginMainFrameMetrics> > details)
libcc.so!cc::SingleThreadProxy::DoPainting(cc::SingleThreadProxy * this)
libcc.so!cc::SingleThreadProxy::BeginMainFrame(cc::SingleThreadProxy *this, const viz::BeginFrameArgs & begin_frame_args)

Raster

  • 模块:cc
  • 进程:Render Process
  • 线程:Raster thread
  • 职责:Raster 阶段会执行每一个 TileTask,最终产生一个资源,记录在产生一个资源,该资源被记录在了 LayerImpl (cc::PictureLayerImpl) 。它会将 DisplayItemList 中的绘制操作 Playback 到 viz 的 CompositorFrame 中。
  • 输入:cc::TileTask
  • 输出:LayerImpl (cc::PictureLayerImpl)

推荐阅读:cc/raster/

这些颜色值位图存储与 OpenGL 引用会在 GPU 的内存中(GPU 也可以进行栅格化,即硬件加速。)

除此之外,Raster 还包括的图片解码的能力:

Raster 的核心类 cc::RasterBufferProvider 有以下几个关键子类:

  • cc::GpuRasterBufferProvider:使用 GPU 进行 Raster,Raster 的结果直接存储在 SharedImage 中。
  • cc::OneCopyRasterBufferProvider:使用 Skia 进行 Raster,结果先保存到 GpuMemoryBuffer 中,然后再将 GpuMemoryBuffer 中的数据通过 CopySubTexture 拷贝到资源的 SharedImage 中。
  • cc::ZeroCopyRasterBufferProvider:使用 Skia 进行 Raster,结果保存到 GpuMemoryBuffer 中,然后使用 GpuMemoryBuffer 直接创建 SharedImage。
  • cc::BitmapRasterBufferProvider:使用 Skia 进行 Raster,结果保存到共享内存中。

GPU Shared Image

所谓 SharedImage 机制本质上抽象了 GPU 的数据存储能力,即允许应用直接把数据存储到 GPU 内存中,以及直接从 GPU 中读取数据,并且允许跨过 shared group 边界。在早期的 Chromium 中使用的的是 Mailbox 机制,如今的模块基本都重构为 GPU Shared Image 了。

GPU Shared Image 包括 Client 端和 Service 端,其中 Client 端可以为 Browser / Render / GPU 进程等,Client 端可以有多个;而 Service 端则只能用一个,运行在 GPU 进程。架构图如下所示:

Chromium 中使用 SharedImage 机制的一些场景:

  • CC 模块:先将画面 Raster 到 SharedImage,然后再发送给 Viz 进行合成。
  • OffscreenCanvas:先将 Canvas 的内容 Raster 到 SharedImage,然后再发送给 Viz 进行合成。
  • 图片处理/渲染:一个线程将图片解码到 GPU 中,另一个线程使用 GPU 来修改或者渲染图片。
  • 视频播放:一个线程将视频解码到 GPU 中,另一个线程来渲染。

光栅化策略

根据 Compositor 和 Raster 这两个阶段是同步进行(注意同步不一定要求在同一个线程)还是异步进行,分为同步光栅化和异步光栅化,而异步光栅化都是分块进行的,因此也叫异步分块光栅化。

同步光栅化,如 Android、iOS、Flutter 都使用的同步光栅化机制,同时它们也支持图层分屏额外的像素缓冲区来进行间接光栅化。

同步光栅化的渲染管线很简单,如下图所示:

异步光栅化则是目前浏览器与 WebView 采用的策略,除却一些特殊的图层外(如 Canvas、Video),图层会进行分块光栅化,每个光栅化任务执行对应图层的对应分块区域内的绘图指令,结果写入该分块的像素缓冲区;此外光栅化和合成不在同一个线程执行,并且不是同步的,如果合成过程中某个分块没有完成光栅化,那它就会保留空白或者绘制一个棋盘格的图形。

两种光栅化策略各有优劣,大致如下表所示:

同步光栅化 异步光栅化
内存占用 极好 极差
首屏性能 一般
动态变化的内容渲染效率
图层动画 一般 惯性动画绝对优势
光栅化性能 低端机略弱

内存占用上,同步光栅化具有绝对的优势,而异步光栅化则很吃内存,基本上可以说浏览器内核的性能大部分是靠内存换出来的。

首屏性能上,同步光栅化的流水线由于更精炼,没有复杂的调度任务,会更早实现上屏。但这个提升实际上也很有限,在首屏性能上,同步光栅化通常比起异步光栅化理论上可以提前一两帧完成,可能就 20 毫秒。(当然,这里异步光栅化的资源也是本地加载的。)

对于动态变化的内容,如果页面的内容在不断发生变化,这意味这异步光栅化的中间缓存大部分是失效的,需要重新光栅化。而由于同步光栅化流水更精炼,这部分重渲染效率也更高一些。

对于图层动画,是异步光栅化绝对的优势了,前文也说了属性树与 Compositing,它可以控制重新渲染的图层范围,效率是很高的。虽然异步光栅化需要额外的分块耗时,但是这个开销不高,也就 2 ms 左右。如果页面动画特别复杂,那么异步光栅化的优势就能体现出来。对于惯性滚动,异步光栅化会提前对 Viewport 外的区域进行预光栅化以优化体验。但是同步光栅化也各显神通,如在编码时,iOS、Android、Flutter 都会非常强调 Cell 层面的重用机制,以此来优化滚动效果。

最后是光栅化的性能上,同步光栅化对性能要求更高,因为需要大量的 CPU 计算,在低端机上容易出现持续掉帧。但是随着手机 CPU 性能越好,同步光栅化策略的优势就越明显,因为对比异步光栅化有着绝对的内存优势,且对于惯性动画也可以通过重用机制来解决,总体优势还是比较明显的。

除此之外,异步光栅化也有一些无法规避的问题如快速滚动时页面白屏、滚动过程中 DOM 更新不同步等问题。

Activate

  • 模块:cc
  • 进程:Render Process
  • 线程:Compositor thread
  • 职责:实现一个缓冲机制,确保 Draw 阶段操作前 Raster 的数据已准备好。具体而言将 Layer Tree 分成 Pending TreeActive Tree,从 Pending Tree 拷贝 Layer 到 Activate Tree 的过程就是 Activate。

核心函数:LayerTreeHostImpl::ActivateSyncTree

Compositor thread 有三棵 cc::LayerImpl 树:

  1. Pending tree: 负责接收 commit,然后将 LayerImpl 进行 Raster
  2. Active tree: 会从这里取出栅格化好的 LayerImpl 进行 Draw 操作
  3. Recycle tree:为避免频繁创建 LayerImpl 对象,Pending tree 后续不会被销毁,而是退化成 Recycle tree。
// Tree currently being drawn.
std::unique_ptr<LayerTreeImpl> active_tree_;

// In impl-side painting mode, tree with possibly incomplete rasterized
// content. May be promoted to active by ActivateSyncTree().
std::unique_ptr<LayerTreeImpl> pending_tree_;

// In impl-side painting mode, inert tree with layers that can be recycled
// by the next sync from the main thread.
std::unique_ptr<LayerTreeImpl> recycle_tree_;

Commit 阶段提交的目标其实就是 Pending 树,Raster 的结果也被存储在了 Pending 树中。通过 Active 可以实现一边从最新的提交中光栅化图块,一边上屏绘制之前的提交。

Draw

该阶段也可以叫做 Submit,本文中统一术语就叫 Draw。

  • 模块:cc
  • 进程:Render Process
  • 线程:Compositor thread
  • 职责:将 Raster 后图块 (Tiling) 生成为 draw quads 的过程。
  • 输入:cc::LayerImpl (Tiling)
  • 输出:viz::DrawQuad

Draw 阶段并不执行真正的绘制,而是遍历 Active Tree 中的 cc::LayerImpl 对象,并调用它的 cc::LayerImpl::AppendQuads 方法创建合适的 viz::DrawQuad 放入 CompositorFrame 的 RenderPass 中。

核心函数:

Viz

这里先介绍一下 Chromium 上屏的重要模块 —— viz。

viz = visuals

在 Chromium 中 viz 的核心逻辑运行在 Viz Process 中,负责接收其他进程产生的 viz::CompositorFrame(简称 CF),然后把这些 CF 进行合成,并将合成的结果最终渲染在窗口上。

viz 模块的核心类如下图所示:

一个 CF 对象表示一个矩形显示区域中的一帧画面, viz::CompositorFrame 内部存储了以下几类数据:

  1. 元数据:CompositorFrameMetadata
  2. 引用到的资源:TransferableResource
  3. 绘制操作:RenderPass/DrawQuad

元数据 viz::CompositorFrameMetadata 记录了 CF 相关的元数据,比如画面的缩放级别,滚动区域,引用到的 Surface 等:

引用到的资源 viz::TransferableResource 记录了该 CF 引用到的资源,所谓的资源可以理解为一张图片。资源有两种存在形式:

  1. 存储在内存中的 Software 资源
  2. 存储在 GPU 中的 Texture

如果没有开启硬件加速渲染,则只能使用 Software 资源;而如果开启了硬件加速,则只能使用硬件加速的资源。

CF 的绘制操作 viz::RenderPass 由一系列相关的 viz::DrawQuad 构成。可以对一个 RenderPass 单独应用特效,变换,mipmap,缓存,截图等。DrawQuad 有很多种类型:

  • viz::TextureDrawQuad:内部引用一个资源。
  • viz::TileDrawQuad:表示一个 Tile 块,和 TextureDrawQuad 类似,内部也引用一个资源,DisplayItemList 会被 cc Raster 为 TileDrawQuad;
  • viz::PictureDrawQuad:内部直接存放 DisplayItemList,但是目前只能用于 Android WebView;
  • viz::SolidColorDrawQuad:表示一个颜色块;
  • viz::RenderPassDrawQuad:内部引用另外一个 RenderPass 的 Id;
  • viz::SurfaceDrawQuad:内部保存一个 viz::SurfaceId,该 Surface 的内容由其他 CompositorFrameSinkClient 创建,用于 viz 的嵌套,比如 OOPIF, OffscreenCanvas 等;

介绍完了 Viz 模块的基础知识,接下来让我们的流水线进入到 Viz Process 中。

Aggregate

  • 模块:Viz
  • 进程:Viz Process
  • 线程:Display Compositor thread
  • 职责:Surface aggregation,接受多个进程传递过来的 CF 并进行合成。

核心类:SurfaceAggregator Display compositor(viz process compositor thread)会接受多个进程传递过来的 CF ,并调用SurfaceAggregator 中的函数进行合成。

Display

  • 模块:Viz
  • 进程:Viz Process
  • 线程:GPU main thread
  • 职责:生成了 CF 以后,viz 会调用 GL 指令把 draw quads 最终输出到屏幕上。
  • 输入:合成之后的 viz::CompositorFrame (需要其中的 DrawQuad)
  • 输出:绘制 pixels

先介绍一下 Viz 的渲染目标,viz::DirectRenderer 和 viz::OutputSurface 用于管理渲染目标,根据它们不同子类的组合,存在三种不同的渲染方案:

  1. 软件渲染viz::SoftwareRenderer + viz::SoftwareOutputSurface + viz::SoftwareOutputDevice
  2. Skia 渲染viz::SkiaRenderer + viz::SkiaOutputSurface(Impl) + viz::SkiaOutputDevice
  3. OpenGL 渲染: viz::GLRenderer + viz::GLOutputSurface

首先是软件渲染,SoftwareRenderer 用于纯软件渲染,当关闭硬件加速的时候使用该种渲染方式。

第二个是 Skia 渲染, SkiaOutputSurface 对渲染目标的控制是通过 SkiaOutputDevice 实现的,后者有很多子类,其中 SkiaOutputDeviceOffscreen 用于实现离屏渲染,SkiaOutputDeviceGL 用于 GL 渲染。

SkiaRenderer 将 DrawQuad 绘制到由 SkiaOutputSurfaceImpl 提供的 canvas 上,但是该 canvas 并不会进行真正的绘制动作,而是通过 skia 的 ddl(SkDeferredDisplayListRecorder) 机制把这些绘制操作记录下来,等到所有的 RenderPass 绘制完成,这些被记录下来的绘制操作会被通过 SkiaOutputSurfaceImpl::SubmitPaint 发送到 SkiaOutputSurfaceImplOnGpu 中进行真实的绘制。

Skia 渲染具有最大的灵活性,同时支持 GL 渲染,Vulkan 渲染,离屏渲染等。

核心函数与相关类梳理如下:

最后是 OpenGL 渲染,来自 viz compositor 线程的 GL 调用(帧数据)被 command buffer 代理,从 Compositor thread 写入到 main thread 的 back buffer 中。

注:GLRenderer 已经被标记为 deprecated, 未来会被 SkiaRenderer 取代。

它使用基于 CommandBuffer 的 GL Context 来渲染 DrawQuad 到 GLOutputSurface 上,GLOutputSurface 使用窗口句柄创建 Native GL Context。GL 调用发生在 Compositor thread 中,通过 InProcessCommandBuffer 这些 GL 调用最终在 CrGpuMain 线程中执行。

关于 CommandBuffer 相关内容可以参考 GPU Command Buffer

最后进行 GL 指令调用,不同的操作系统/显卡驱动提供的类库不同,

需要注意的,图形绘制引擎一般会使用双缓冲(Double Buffering)技术,先将图片绘制到一个缓冲区,再一次性传递给屏幕进行显示,这样可以防止屏幕抖动,优化渲染性能。

同样的这里上屏也分为 Front Buffer(前台缓冲区)与 Back Buffer(后台缓冲区),屏幕负责从 Front Buffer 读帧数据输出展示。viz 调用 Display::DrawAndSwap 来交换 Front Buffer 与 Back Buffer 的指针,在垂直同步信号来临时,显卡驱动类库执行对应的绘制指令,最后用户就能在屏幕上看到 pixels 了。

以上便是 Chromium 的整个渲染流水线,本文是渲染流水线的 Overview,若对该话题感兴趣,可期待该系列的后续文章,或阅读本文列出的核心源码与扩展阅读,来进一步了解更多相关知识。

扩展阅读

周刊(第13期):玄学杠杆与异世界小组

玄学杠杆与异世界小组

周末听到一期播客,说的是年轻人的网络占卜:EP03 网络占卜「奇观」:00 后给 70 后指点人生 - 跳进兔子洞

网络占卜变成了年轻人的职业选择,大多数人会说一切的开始来源于一颗纯粹的好奇心。低门槛、零成本、高回报。大部分占卜师的第一步都是自学,基本上去 B 站或者几本书看个两三天就会了。小红书上接单的普遍是自学了几个月的 00 后,想要从零基础把塔罗发展成副业甚至是全职并不难,而且比较有前景。现在的年轻人社恐的略多,可能这种足不出户、不用面对面的要求会比较符合目前的市场环境。

但是这期播客节目深入研究之后,发现其实客户分布在 25 岁到 55 岁之间的,年轻的客户群占卜的话题总是离不开「爱情」,40 多岁的群体则会关注家庭和孩子问题。但是这些群体有个共同特种,很多时候,客户想要的是一种纯粹的情感交流和倾诉。而占卜师,更像是一个树洞。

因为她们基本上是没有办法排解的。有的时候又不能跟父母说,也不能跟朋友说,也没有自己的资源和资金去寻求真正专业的、长期的心理帮助。这个时候可能就占卜师可以起到一个比较微小的治愈作用。 ——《网络占卜「奇观」》

听完这期节目,我还想到之前看到的袤则咨询的 2022大社交趋势观察报告,里面提到了一个趋势「玄学杠杆」。

迷茫的人们将对未知人生的情感投射于漫天星斗与风流水转,以“信而不迷”的态度将玄学纳入日常生活决策链的关键一环。 ——《2022大社交趋势观察报告》

玄学杠杆有以下三个特点:

  1. 将玄学作为小的生活情趣
  2. 信而不迷
  3. 借玄学完整自我认知教育

当代玄学更像是一种积极心理学的工具——给定一个不可证伪的支点,年轻人似乎就能撬起阻碍人生、压抑命运的巨石。但同时人们也将自己作为“万物的尺度”,拒绝出让自己的能动性:不好的签就是不准,不应的庙就是不灵。正是因为感到困惑迷惘,年轻人从而寻求玄学的助力,如果事与愿违,更加无力,他们便不会沉溺其间。——《2022大社交趋势观察报告》

此外,接纳玄学的年轻人也不是单纯去祈求一个问题的答案,正如上面的播客分析到的,有时候他们更期待能够有一个树洞,借由玄学的话语体系,在因时而变的命理中完成自我认知的教育。在与玄学的对话中,对“我为什么会这样”进行追溯和省思,从而找到自我认同的坐标。

网络占卜可以说是社会异化劳动压力下的积极心理学工具,类似的现象还有去年大火的豆瓣异世界小组:

这些小组不仅以话题与兴趣为纽带组织起了交往的论坛,甚至直接以小组为基础建立起与现实世界平行存在的“异世界”,令人叹为观止。

豆瓣异世界小组:人类对穿越时空的想象,蕴藏在各种媒介奇观里 一文中分析了为什么会有异世界小组这种现象:

  1. 从独白到狂欢:社交媒体的形塑
  2. 从想象到行动:具身参与的诱惑
  3. 从逃离到回归:对生存境况的反思

第一点说的是社交媒体的集体狂欢。

第二点说的是「具身性」,换言之,置身其中的人们仿佛加入了一场电子游戏一般,通过发帖、回帖以及创建规则等方式操控着处于异世界中的“身体”。如此,流于想象的时空重组成为了可感可触的具身参与,这正是异世界最引人流连忘返之处。

而第三点,则和玄学杠杆很是类似,是对现实生活的逃离,而逃离行为本身就有反思现实的意味。

一方面,城市化带来的生活原子化致使现代人的孤独感与离群感上升。另一方面,层出不穷的新社会议题涌现在公众视野,凸显着诸种社会问题。——《豆瓣异世界小组:人类对穿越时空的想象,蕴藏在各种媒介奇观里》

根据马克思的观点,人在劳动过程中,由于自身的活动而产生出了与自己相对立、制约自己的东西,即所谓的异化劳动。当日益增长的异化劳动压力将年轻人裹挟,客观现实的不确定性与主观个体的徒劳无力感,使得部分年轻人将当代玄学视为一种提供情绪价值的积极心理学工具。

异世界小组与网络占卜恰是这类工具的典型代表。

在社交平台上,年轻人将自己的异化困境诉诸于玄学同好,交换倾诉,彼此共情,互相安慰,共同寄托——某种程度上,“搞玄学”与各种娱乐休闲的消遣方式一样,是年轻人消解负面情感的重要路径。——《2022大社交趋势观察报告》

但除此之外,在异世界小组与网络占卜这类诞生于社交媒体这一奇幻之地的诸种媒介奇观,其实都是人类的实践与社交媒体诸多技术潜力相遇而迸发的火花,而这些事物同时正在形塑着我们的日常生活,为我们创造着新的想象空间。

想到《娱乐至死》中尼尔·波兹曼的「媒介即隐喻」理论:媒介的选择会反过来改变我们自己,我们选择什么样的媒介,就通往什么样的结局。

我们不知道该如何认识自我,那么就创造玄学的镜子与异世界的光景,通过投射与自己互动。以此补足自我认知,丰满个体意义。

珍惜当下的日常,向内看、向后看,也是为了更好地向外看、向前看。不管怎样,多读书、多思考永远是正途。

每周推荐

技术文:How Blink works

这周在看 Blink 的代码,从网上找到一篇很不错的文章:How Blink works,这是一篇 Chromium 官方团队出品的文章,它介绍了 Chrome 浏览器内核内部的重要模块 Blink 内部设计和实现的一些细节。对于想要了解 Chrome 内核内部实现的同学,这篇文章提供了不错的入门指引。

本周记录

Recent Viewings

  • 读完:科幻 |《记忆旅行者》| ★★★★☆
  • 看过:电影 |《未麻的部屋》| ★★★★★
  • 看过:电影 |《爱在日落黄昏时》| ★★★★☆
  • 看过:电影 |《红辣椒》| ★★★★★
  • 玩过:治愈 |《Dorfromantik》| ★★★★☆

很喜欢 Dorfromantik 这种治愈画风的游戏呀~

Recent Code

TypeScript        21 hrs 1 min   █████████▋░░░░░░░░░░░  45.9%
TypeScript React  19 hrs 56 mins █████████▏░░░░░░░░░░░  43.6%
C++               1 hr 21 mins   ▋░░░░░░░░░░░░░░░░░░░░   3.0%
JSON              1 hr 8 mins    ▌░░░░░░░░░░░░░░░░░░░░   2.5%
JavaScript        56 mins        ▍░░░░░░░░░░░░░░░░░░░░   2.0%

前端开发中的大小写敏感问题

大小写敏感(case sensitivity)是软件开发领域的议题,指同一个“词”拼写的大小写字母的不同可能会导致不同效果的场景。

接下来,我们谈谈前端开发领域中一些常见的大小写敏感/不敏感的场景:

  • HTTP Header
  • HTTP Method
  • URL
  • Cookie
  • E-Mail Address
  • HTML5 Tags and Attribute name
  • CSS Property
  • File's name in Git

HTTP Header 区分大小写吗?

HTTP Header 的名称字段是不区分大小写的。

RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1)#section-3.2 中规定:

Each header field consists of a case-insensitive field name followed by a colon (":"), optional leading whitespace, the field value, and optional trailing whitespace.

但是需要注意的是,HTTP/2 多了额外的限制,因为增加了头部压缩,要求在编码前必须转成小写。

RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2)#section-8.1.2 中规定:

However, header field names MUST be converted to lowercase prior to their encoding in HTTP/2.

而如今大多数客户端与 HTTP 服务端都默认会把 HTTP Header 的名称字段统一改成小写,避免使用方再重复做大小写转换的处理逻辑。

比如 NodeJS 中的 HTTP 模块就会自动将 Header 字段改成小写 node/_http_outgoing.js at main · nodejs/node · GitHub

// lib/_http_outgoing.js
ObjectDefineProperty(OutgoingMessage.prototype, '_headers', {
__proto__: null,
get: internalUtil.deprecate(function() {
return this.getHeaders();
}, 'OutgoingMessage.prototype._headers is deprecated', 'DEP0066'),
set: internalUtil.deprecate(function(val) {
if (val == null) {
this[kOutHeaders] = null;
} else if (typeof val === 'object') {
const headers = this[kOutHeaders] = ObjectCreate(null);
const keys = ObjectKeys(val);
// Retain for(;;) loop for performance reasons
// Refs: https://github.com/nodejs/node/pull/30958
for (let i = 0; i < keys.length; ++i) {
const name = keys[i];
headers[StringPrototypeToLowerCase(name)] = [name, val[name]];
}
}
}, 'OutgoingMessage.prototype._headers is deprecated', 'DEP0066')
});

PS. 在写这篇文章的时候,发现 NodeJS 的这部分文档有处错误,示例中的获取 headers 存在大写字母,于是顺手提了个 PR,现已经合入了。doc: fix typo in http.md by airingursb · Pull Request #43933 · nodejs/node · GitHub

除此之外,Rust 的 HTTP 模块也会将 Header 的字段名默认改成小写,理由是 HeaderMap 会处理地更快。

文档可见http::header - Rust

The HeaderName type represents both standard header names as well as custom header names. The type handles the case insensitive nature of header names and is used as the key portion of HeaderMap. Header names are normalized to lower case. In other words, when creating a HeaderName with a string, even if upper case characters are included, when getting a string representation of the HeaderName, it will be all lower case. This allows for faster HeaderMap comparison operations.

源码可见:src/headers/name.rs - http 0.1.3 - Docs.rs

作为前端开发,会更加关注 Chromium 和 WebKit 对这块的处理。

对于请求头而言,我们在 Chrome 中做个实验:

var ajax = new XMLHttpRequest();
ajax.open("GET", "https://y.qq.com/lib/interaction/h5/interaction-component-1.2.min.js");
ajax.setRequestHeader("X-test", "AA");
ajax.send();

对于 HTTP2 的请求,抓包之后可以发现,这里的 X-test 被改写成了小写 x-test

为了严谨起见,这里又使用了 Chrome 自带的 netlog-viewer 去抓包查看,这里的请求头确实是被转成小写了没有问题:

注:以上测试在 Safari 中也会得到同样的结果,但是必须要抓包看,如果只是在 Safari 的控制台看,它展示大小写是有问题的,而 Chrome 的 Devtools 则没有问题。这里猜测是 Safari 的 Bug。

于是去阅读 Blink 中 XMLHTTPRequest 的源码,可惜的是没有找到在哪里转成了小写。

我也去查 XMLHTTPRequest 的标准,XMLHttpRequest Standard-method) 中也没有提到需要将 header 字段名改成小写。

甚至在 XHR 标准中也曾有人建议过直接在 XHR 把 Header 字段名转成小写(Should XHR store and send HTTP header names in lower case? · Issue #34 · whatwg/xhr · GitHub),但是被拒绝了。

按照规范 HTTP2 的 Header 名都需要转成小写,但我找了个 HTTP1.1 的站点测试了一下:

var ajax = new XMLHttpRequest();
ajax.open("get", "http://www.cn1t.com/airing.js");
ajax.setRequestHeader("X-test", "AA");
ajax.send();

发现这里的 Header 字段名则并不会转成小写:

可以得知,请求头的字段名大小写转换不是 XMLHTTPRequest 做的事情,而是底层网络库的逻辑。

PS. 我这里 Debug 了许久,没有找到更底层具体是哪里转成了小写,若有知晓的同学可以直接评论告知,万分感谢。

而响应头中的字段名则不一样了。如果你使用 XMLHTTPRequest 的 getAllResponseHeaders 等方法去获取响应头,Blink 会将 Header 的名称字段改成小写:

String XMLHttpRequest::getAllResponseHeaders() const {
// ...
for (const auto& header : headers) {
string_builder.Append(header.first.LowerASCII());
string_builder.Append(':');
string_builder.Append(' ');
string_builder.Append(header.second);
string_builder.Append('\r');
string_builder.Append('\n');
}
// ...
}

此外,Chromium 在解析网络响应包的时候,如果走 HTTP2 协议,发现了 Header 字段名有大写字母,会直接导致网络包解析失败:

absl::optional<ParsedHeaders> ConvertCBORValueToHeaders(
const cbor::Value& headers_value) {
// |headers_value| of headers must be a map.
if (!headers_value.is_map())
return absl::nullopt;
ParsedHeaders result;
for (const auto& item : headers_value.GetMap()) {
if (!item.first.is_bytestring() || !item.second.is_bytestring())
return absl::nullopt;
base::StringPiece name = item.first.GetBytestringAsString();
base::StringPiece value = item.second.GetBytestringAsString();
// If name contains any upper-case or non-ASCII characters, return an error.
// This matches the requirement in Section 8.1.2 of [RFC7540].
if (!base::IsStringASCII(name) ||
std::any_of(name.begin(), name.end(), base::IsAsciiUpper<char>))
return absl::nullopt;
// ...other
}
return result;
}

HTTP Method 区分大小写吗?

根据规范 RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1)#section-3.1.1

The method token indicates the request method to be performed on the target resource. The request method is case-sensitive.

HTTP Method 是区分大小写的,并且全部为大写。

但其实如果你的 XMLHttpRequest 实例传入小写的 Method 也是没有关系的(如调用 ajax.open("get", "https://ursb.me")),Blink 等浏览器内核会将其规范化改成大写 Source/core/xmlhttprequest/XMLHttpRequest.cpp - chromium/blink - Git at Google

void XMLHttpRequest::open(const AtomicString& method,
const KURL& url,
bool async,
ExceptionState& exception_state) {
//...
method_ = FetchUtils::NormalizeMethod(method);
// ...
}
AtomicString FetchUtils::NormalizeMethod(const AtomicString& method) {
// https://fetch.spec.whatwg.org/#concept-method-normalize
// We place GET and POST first because they are more commonly used than
// others.
const char* const kMethods[] = {
"GET", "POST", "DELETE", "HEAD", "OPTIONS", "PUT",
};
for (auto* const known : kMethods) {
if (EqualIgnoringASCIICase(method, known)) {
// Don't bother allocating a new string if it's already all
// uppercase.
return method == known ? method : known;
}
}
return method;
}

URL 区分大小写吗?

根据规范 RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1)#section-2.7.3 中的描述:

The scheme and host are case-insensitive and normally provided in lowercase; all other components are compared in a case-sensitive manner.

HTTP 协议 (scheme / protocol) 和域名 (host) 不区分大小写,但 path、query、fragment 是区分的。

在 Chromuim 中,URL 统一使用 kUrl 类管理,其内部会对 protocolhost 做规范化处理(canonicalization),这个过程会将 protocol 与 host 改为小写。

除此之外,其中比较有意思的是 path,虽然规范约定了 path 是区分大小写的,但实际情况却取决于 Web Server 的底层文件消息,因此 path 有可能也是不区分大小写的

比如 IIS 服务器就是不区分的,因为它取决于 Windows 的文件系统,Windows 使用的 NTFS 和 FAT 系列文件系统,默认大小写不敏感(但大小写保留)。对应的,如果 Apache 服务器部署在大小写不敏感的 Mac(HFS) 上,也同样不区分大小写。

扩展介绍一些主流操作系统的底层文件系统:

  • Windows 使用的 NTFS 和 FAT 系列文件系统,默认大小写不敏感,但大小写保留
  • macOS 使用的 APFS 和 HFS+ 文件系统,默认大小写不敏感,但大小写保留
  • Linux 使用的 ext3/ext4 文件系统,默认大小写敏感

除此之外,还和服务器的策略有关系,比如 https://en.wikipedia.org/wiki/Case_sensitivityhttps://en.wikipedia.org/wiki/case_sensitivity 指向同一篇文章,但是就不能简单认为它大小写不敏感,因为 https://en.wikipedia.org/wiki/CASE_SENSITIVITY 就直接 404 了。

Cookie 区分大小写吗?

先说结论,符合直观,Cookie 的名称是区分大小写的。但是规范的演进比较坎坷。

早期的规范 RFC 2109 - HTTP State Management Mechanism Cookie 的名字是不区分大小写的:

Attributes (names) (attr) are case-insensitive. White space is permitted between tokens. Note that while the above syntax description shows value as optional, most attrs require them.

而在 RFC 2965 则废弃掉了 RFC 2109

This document reflects implementation experience with RFC 2109 and obsoletes it.

但是在 Cookie 的最新规范 RFC 6265 中并没有写明 Cookie 是否区分大小写,那么默认可以认为是区分的,主流浏览器 Chrome 与 FireFox 的实现也都是区分 Cookie 的大小写。

E-Mail 地址区分大小写吗?

根据规范 RFC 5321: Simple Mail Transfer Protocol#section-2.3.11

The standard mailbox naming convention is defined to be "local-part@domain"; contemporary usage permits a much broader set of applications than simple "user names". Consequently, and due to a long history of problems when intermediate hosts have attempted to optimize transport by modifying them, the local-part MUST be interpreted and assigned semantics only by the host specified in the domain part of the address.

而 domain 部分遵循 RFC 1035: Domain names - implementation and specification#section3.1:

"Name servers and resolvers must compare [domains] in a case-insensitive manner"

综上所述:邮箱中的域名不区分大小写,而用户名(local-part)是否区分大小写,则取决于电子邮件服务商。

HTML5 标签和属性名区分大小写吗?

根据规范 HTML Live Standard#section-13.1HTML 标签和属性名不区分大小写

Many strings in the HTML syntax (e.g. the names of elements and their attributes) are case-insensitive, but only for characters in the ranges U+0041 to U+005A (LATIN CAPITAL LETTER A to LATIN CAPITAL LETTER Z) and U+0061 to U+007A (LATIN SMALL LETTER A to LATIN SMALL LETTER Z). For convenience, in this section this is just referred to as "case-insensitive".

这意味着文档类型 <!DOCTYPE html> 写成 <!doctype html> 也是可以的。

需要注意的是,data attribute 是个例外,它必须要小写。HTML Live Standard#section3.2.6.6

A custom data attribute is an attribute in no namespace whose name starts with the string "data-", has at least one character after the hyphen , is XML-compatible, and contains no ASCII upper alphas.

Blink 有纠错逻辑,即便写了 <div data-Name="airing"></div>,最后也会被转成 <div data-Name="airing"></div>。但考虑到兼容性,这里的属性名还是需要小写 data-name 的。

CSS 区分大小写吗?

根据 CSS Selectors Level 3

All Selectors syntax is case-insensitive within the ASCII range (i.e. [a-z] and [A-Z] are equivalent), except for parts that are not under the control of Selectors. The case sensitivity of document language element names, attribute names, and attribute values in selectors depends on the document language. For example, in HTML, element names are case-insensitive, but in XML, they are case-sensitive. Case sensitivity of namespace prefixes is defined in [CSS3NAMESPACE].

CSS 的选择器语法不区分大小写,而属性名与属性值是否区分大小写,取决于所在的文档语言。 如在 XHTML DOCTYPE 中它们区分大小写,但是在 HTML DOCTYPE 则不区分。

Git 文件名区分大小写吗?

Git 默认不区分文件名大小写

因此,如果我们平常不注意文件的大小写,在实际使用中可能会遇到这样的问题:

  1. 如果团队中有人在 Linux 系统或者开启文件系统大小写敏感的 macOS 或 Window 上开发,他无视了已经存在的 RankItem.tsx 文件,创建了新的 rankItem.tsx 文件,并提交成功了;
  2. 那么此时 Git 服务器上同时存在 RankItem.tsxrankItem.tsx 文件,在 Windows 或 macOS (默认文件系统)上开发的人,则无法正常拉取到这两个文件。

这里建议关闭 Git 的忽略大小写功能:

git config --global core.ignorecase false

同时在 Windows 或 macOS 上重命名大小写时,使用 git mv

git mv --force rankItem.jsx RankItem.jsx

如果没有开启的话,这里举个例子,将 readme.md 改成 README.md,这个时候 git status 无法检测到更变记录:

如果用 git mv,则是可以正常检测到的:

因此在开发的时候,强烈推荐关闭 Git 大小写忽略的配置,并且使用 git mv 进行重命名操作。若条件允许的话,亦可以修改 macOS 或 Windows 默认的文件系统,将卷宗的文件系统改成大小写敏感。


以上便是前端开发中的一些大小写敏感问题,总结一下:

  • HTTP Header 的 key 不区分大小写,但是绝大多数框架会将响应头的 key 改成小写。[RFC 7230]
  • HTTP2 则因为头部压缩,要求必须小写,浏览器会将其改成小写。[RFC 7540]
  • HTTP Method 区分大小写,且必须为大写。[RFC 7230]
  • URL 的 protocol 和 host 不区分大小写,浏览器会自动改成小写。[RFC 7230]
  • URL 的 path 按规范而言是区分大小写的,但实际是否区分大小写取决于 Web Server 的文件系统和服务端配置。[RFC 7230]
  • URL 的 query 与 fragment 是区分大小写的。[RFC 7230]
  • Cookie 的 key 是区分大小写的,虽然规范并没有明说这点。 [RFC 6265]
  • E-Mail 的域名部分是不区分大小写的 [RFC 1035]
  • E-Mail 的用户名部分是否区分大小写,取决于邮件服务商。 [RFC 5321]
  • HTML5 的标签名和一般的属性 key 是不区分大小写的。[HTML Live Standard 13.1]
  • HTML5 的 data- 属性是区分大小写的。[HTML Live Standard 3.2.6.6]
  • CSS 的选择器语法不区分大小写,而属性名与属性值是否区分大小写,取决于所在的文档语言。[CSS Selectors Level 3]
  • Git 默认是忽略文件大小写的。
  • Windows 使用的 NTFS 和 FAT 系列文件系统,默认大小写不敏感,但大小写保留。
  • macOS 使用的 APFS 和 HFS+ 文件系统,默认大小写不敏感,但大小写保留。
  • Linux 使用的 ext3/ext4 文件系统,默认大小写敏感。

周刊(第12期):前端三大浪漫

前端开发中的大小写敏感问题

由于前段时间被催更了技术文,于是这周末写了一篇技术文章 前端开发中的大小写敏感问题 - Airing 的小屋,因此本期周刊主体部分便不再撰写。

有兴趣的同学可以直接阅读原文: 前端开发中的大小写敏感问题 - Airing 的小屋

每周推荐

前端三大浪漫

看到一篇文章介绍了 脉脉上的 "前端三大浪漫" 是个啥?,三大浪漫分别是:

  1. 富文本编辑器
  2. 在线 excel
  3. CRDT - 无冲突复制数据类型

路径依赖

朋友上周给我推荐了这篇文章,写的蛮好的:我们当下的媒介使用习惯,是否在很久以前就养成了?

生活中有一些路径依赖的案例:

  • QWERTY 键盘:明明效率并不是最高的,但是沿用至今。
  • 高铁座位号是 ABC-EF:缺少了 D,因为路径依赖 AF 表示靠窗。
  • 一首歌的时长通常在 5 分钟以内:因为早期黑胶唱片的容量限制必须控制在5分钟之内。

至于路径依赖的作用,文章说道:

过去经验、传统惯习或集体记忆,可以理解为社会的保持器,帮助社会成员产生群体归属感,具有确立身份认同或社会认同的重要功能。从时空角度来看,它们会为社会成员提供整体想象和历史意识,具有共享文化意义、传承文化传统的功能。这已经超越了路径依赖的范畴,而关乎一个社会如何记忆它自己

这篇文章还从个体与社会两个层面去分析了路径依赖产生的原因,值得细细研读一下。后续的周刊可能还会再深入谈一谈「路径依赖」这个话题。

本周记录

Recent Viewings

  • 在读:科幻 |《记忆旅行者》
  • 在看:动漫 |《夏日重现》
  • 在看:漫画 |《间谍过家家》
  • 看过:电影 |《爱在黎明破晓前》| ★★★★★
  • 看过:电影 |《我是谁:没有绝对安全的系统》| ★★★★☆
  • 玩过:手游 |《英雄联盟-电竞经理》

Recent Code

TypeScript React  17 hrs 45 mins █████████▎░░░░░░░░░░░  44.3%
TypeScript        15 hrs 7 mins  ███████▉░░░░░░░░░░░░░  37.7%
JSON              1 hr 34 mins   ▊░░░░░░░░░░░░░░░░░░░░   3.9%
C++               1 hr 32 mins   ▊░░░░░░░░░░░░░░░░░░░░   3.8%
JavaScript        1 hr 18 mins   ▋░░░░░░░░░░░░░░░░░░░░   3.3%

周刊(第11期):筮法是如何进行的

筮法是如何进行的?

前几天看到一个视频 《周易》是咋拿来“算卦”的 ,发现这个算卦的流程挺有意思的(啊,中哲专业的血脉突然又开始躁动了),学习了下做个笔记。

古时算卦叫“卜筮”,“卜”是利用龟壳上的裂纹来算卦,但是太过久远具体如何去算现在已无考证;“筮”则是利用蓍草(蒿)进行随机组合进行测算。具体流程如《周易·系辞上传》所言:

大衍之数五十,其用四十有九。分而为二以象两,挂一以象三, 揲之以四以象四时,归奇于扐以象闰,五岁再闰,故再扐而后挂。 天一地二,天三地四,天五地六,天七地八,天九地十。天数五,地数五,五位相得而各有合。天数二十有五,地数三十,凡天地之数五十有五。此所以成变化而行鬼神也。

准备阶段:“大衍之数五十,其用四十有九。”

  1. “大衍之数五十”:先拿 50 根蓍草,表示先天无极的大衍之数。
  2. “其用四十有九”:随机拿出一根,剩下 49 根,拿出的那一根表示后天之“损”。阴阳运行的太极要在不平衡的奇数里进行运算。

测算阶段:“分而为二以象两,挂一以象三, 揲之以四以象四时,归奇于扐以象闰。”

“分二”、“挂一”、“揲四”、“归奇”表示“四营”,即要做四个步骤进行测算:

  1. 分二:就是把 49 根蓍草随机分成两组,两组分别象征“天”、“地”。即“分而为二以象两”。
  2. 挂一:随机从“天”或“地”这两组里拿出一根,这叫“挂一”,拿出的那一根就是“人”。所以原文“挂一以象三”中的三代表“天地人”。这个时候就只剩下 48 根了。
  3. 揲四:之后把“天”这一组按照 4 个为一小组进行清点,多余不满 4 个被排除在外;“地”这一组也是一样的。那么这个时候剩下的蓍草一共有 50-(1+1+(天 mod 4)+(地 mod 4)) 根。这里的四就象征着四时。
  4. 归奇:把剩下的蓍草整理一下,重新进行“四营”循环。这样的循环总共要进行三遍,所谓“四营三变”。

三次之后,数一数最后还剩多少根蓍草。其实通过这样的步骤算下来之后,剩下的蓍草只有 4 种可能性:36、32、28、24(概率分别是 3/16,7/16,5/16,1/16)。如果把它们除以 4,就会得到 9、8、7、6 四个可能。那么 9、8、7、6 就是这次算卦算出的结果,即四象。

根据“奇为阳,偶为阴”:

  • 9:太阳,最大的阳
  • 7:少阳,较小的阳
  • 8:少阴,较小的阴
  • 6:太阴,最大的阴

我们再把这次的结果转成的爻:

  • 阳爻:—,9,7 可转阳爻
  • 阴爻:- -,6,8 可转阴爻

可以发现得到阴爻((1+7)/16)和阳爻((3+5)/16)的概率都是 1/2。

每三个阴阳爻排列组合就能得出一个八卦中的基本挂,所以要上述的筮法要再额外进行两次,才能得出剩下的两个爻组成一个基本挂。

假设三轮的结果是“7,8,7”,那么卦象是 “阳阴阳”,“离卦”。但是 6 和 9 属于特殊情况,所谓“物极必反”,那么它就会“变卦”,比如 666 的太阳爻“坤卦”反而会变成 999 的太阴挂“乾卦”(当然,还有一种说法是“阳极化少阴,阴极化少阳”,待后续考证);7、8 则不会变卦,符合当前状态。

得到八卦之后,还要去计算全卦。所谓全卦是两个八卦的上下组合,即 8x8=64 可能。即一共要有 6 个爻组成,要筮法 6 轮。

假设结果是 978976,那么得到就是下巽上巽的“巽卦”;那么 96 变卦之后得到是“节卦”,那么我们称这次的结果就是“本卦巽,变卦节”。之后拿出《周易》去查解释就行了。

每周推荐

Hook: 关联任意两个文件

Hook – Links beat searching 是MacOS 上的一款付费 App,支持关联两个文件,原理是关联对应文件类型的 open scheme,这也就是 Hook 的含义。

我的主要使用场景有以下两个:

  1. 关联:需求笔记+需求单+设计稿+技术方案
  2. 关联:代码+代码笔记+技术方案

当我打开其中任意一个文件,都可以按住 "Ctrl+H" 查看至于关联的文件/链接。

当然,这个软件还有一部分很大的场景是关联 GTD 的任务项与相关文件(可惜我正在用的滴答清单不支持 scheme):

Emojimix

emojimix 是一个网页应用,可以混合两个 Emoji 得到一个新的 Emoji,贼有意思。

举两个栗子:

本周记录

Recent Viewings

  • 在看:动漫 |《宝可梦旅途》
  • 读完:小说 |《沉默的巡游》| ★★★☆☆
  • 看过:比赛 | IEM - Navi vs AST | ★★★★★
  • 看过:动漫 |《间谍过家家》| ★★★★★
  • 看过:电影 |《土拨鼠之日》| ★★★★★
  • 看过:电影 |《最佳出价》| ★★★★★
  • 看过:电影 |《误杀瞒天记》| ★★★★★
  • 看过:电影 |《花束般的恋爱》(二刷)| ★★★★★
  1. 《宝可梦旅途》:傻东西 100% 胜率的千万伏特居然用在了八强赛的第一场,那么大胆猜测决赛是丹喷 vs mega 路卡利欧,小智今年又没法夺冠了。
  2. 《沉默的巡游》:设定不错,但是剧情转折却没有那么自然流畅,有点强行为了反转而反转的感觉。
  3. 昨晚 Navi 的比赛看的是热血沸腾,现场的气氛也是非常活跃。Navi:“3 分保平,4 分保赢。”
  4. 《间谍过家家》:完结了,据说第二季要等到 10 月了,可惜邦德第一季居然还没有登场。
  5. 《土拨鼠之日》:直到现在才看,原本以为是恐怖片,没想到是 HE 的喜剧。人生确实就是平凡的日子一遍又一遍的重复,如果说楚门是困在了空间里打破束缚,那么菲尔就是被困在了时间里打破日常。每天改变自己一点,让自己更好一点,去爱去生活。
  6. 《最佳出价》:看完之后给我的震撼实在是太大了,不愧是意大利的悬疑片,强烈安利。

Recent Code

TypeScript 28 hrs 49 mins ██████████████████▏░░  86.6%
JSON       1 hr 41 mins   █░░░░░░░░░░░░░░░░░░░░   5.1%
JavaScript 58 mins        ▌░░░░░░░░░░░░░░░░░░░░   2.9%
HTML       43 mins        ▍░░░░░░░░░░░░░░░░░░░░   2.2%
YAML       26 mins        ▎░░░░░░░░░░░░░░░░░░░░   1.3%

PS. 这周不知道是不是 WakaTime 出 Bug 了,TypeScript React 居然没统计到…

周刊(第10期):那些我喜欢的游戏(第1辑)

周刊居然坚持了两个月,一期不落按时按量写到了第 10 期,为自己鼓个掌~

我梳理了一份数据表,把我推荐过的产品、游戏、文章都放进去了,并且之后会不断更新:Airing - Best Product;此外我也弄了份收集表,如果你有想推荐的产品,欢迎填写表格,我会再分享给大家。毕竟,好东西就是要分享的嘛!

你也可以扫码填写表格:

那些我喜欢的游戏(第1辑)

以下排名没有先后。

1. Undertale

你拒绝伤害任何人。 就算你选择逃跑, 你的脸上也带着微笑。

  • 中文名:传说之下
  • 平台:Steam / Nintendo Switch
  • 时长:6 小时(一周目)
  • 类型:像素风,RPG,剧情,音乐
  • 主题:温情(or 邪恶?)/ 感人
  • 获奖:无数

剧情简介:

很久以前,地球由两个种族均等统治着,分别为人类和怪物 。一日,战鼓击碎了双方之间的和平;在人类近似屠杀式的侵略下,人类大获全胜。人类将怪物驱逐到地下世界,并由七位术士设下了相当强力的魔法结界,将怪物困在地下世界。人们可以在伊波特山进入下界,而须至少需要人类与怪物的灵魂各一个才可离开下界。在故事开始前,没有一个人类活着离开下界过,使伊波特山有登山者有去无回的传闻。

怪物被困在地下世界中,由 Asgore Dreemurr 统治。怪物死亡后躯体会立即化为灰烬,不留下灵魂或尸体。怪物的灵魂远没有人类强大。


这个游戏我写过评测,它还是我在知乎的第一个回答。为了不剧透大家一脸,我就不贴评测链接啦。这是一个很感人的故事,音乐也太太太太出色了,全程玩下来,感觉自己并不是在玩游戏,而是走过了一个善良、温暖的人生。4年后再忆起,仍能想起他们的音容笑貌,以及那段和他们一起经历的冒险。

这是一个当之无愧的满分游戏。

PS. 有些战斗比较难,对手残党不太友好,但是我还是不建议大家一周目的时候看攻略。Switch 版有些贵,而且没有中文,建议购买 Steam 版。

2. To the moon

如果你忘记了,或是走丢了呢? 那么我们总会在月亮上相遇的。

  • 中文名:去月球
  • 平台:Steam / iOS / Android
  • 时长:3 小时
  • 类型:像素风,剧情,音乐,独立作品
  • 主题:爱情,记忆,人生

剧情简介:

在《去月球》的世界里,存在着借由改变记忆为弥留之际的人们完成愿望的服务。由于这种人工记忆是永久的,在病人醒来的时候会与病人的真实记忆产生冲突,也因此这种技术只能对临死的人实施。

故事开始于提供这项服务的西格蒙德公司的两位职员伊娃·罗莎莉恩博士与尼尔·沃茨博士准备为垂死的约翰尼·威尔士实现一生的梦想:登陆月球,虽然约翰尼并不知道自己想前往月球的理由。两位博士为了完成这项任务进入约翰尼的记忆中,借由一个个约翰尼生命中的重要物品回朔时间。他们随着见证了约翰尼记忆中的一些重要时刻,渐渐地了解成就约翰尼当前惨状的缘由。在两位博士终于到达约翰尼的童年的之际,他们试图为约翰尼注入去月球的愿望,让约翰尼的内心制造基于这个愿望的新的人生与记忆,以求约翰尼死时没有任何遗憾。

然而,并非一切都照计划进行,两位医生发现约翰尼的愿望、过去和他已故的妻子莉娃都藏着巨大的玄机。随着时间的一分一秒的流失,罗莎莉恩博士和瓦茨博士必须在约翰尼辞世前解开他过去的秘密,并不惜一切代价将他送往月球。


《去月球》也是一个很感人的故事,值得称道的是,音乐、剧情、编程、美术,全都是作者一个人完成的!音乐超级好听,很多歌我都单独下载下来听了。剧情也是一级棒,完全是一个可以拍成电影的剧情。据说会出第二部,但是并不期待,因为这一部的结局,已经足够完美了。

这是一个游戏,一个好故事,一段纯粹的爱情。如果人生可以重来,我依然会选择爱上你。我相信,这一切都将教会你,“爱”的意义。

PS. 这个游戏的收集环节个人认为设计的不够好,如果真的找不到东西的话还是看看攻略吧,不能让收集环节打乱自己看故事的节奏~

3. Don't Starve (Together)

  • 中文名:饥荒 & 饥荒联机版
  • 平台: Steam / WeGame / iOS
  • 时长:∞
  • 类型:RPG,合作,生存

剧情简介:

《饥荒》的故事讲述的是关于一名科学家威尔逊被恶魔传送到了异世界荒野。他必需用本人的聪慧在残酷的野外环境中求生。在《饥荒》里,玩家必须利用异世界中的自然资源让自己存活下去,并且抵御各种异世界生物的威胁。


游戏很好玩,可玩性很高,尤其是联机版,以前和朋友一起玩。我觉得这个游戏还是大家一起玩会比较欢乐,如果只是玩单机版,会更偏向于生存,所以,也会有些孤独。

PS. 新手还是先看一下攻略,不然会尝试到各种死法,游戏体验不好…

4. Darkest Dungeon

  • 中文名:暗黑地牢
  • 平台:Steam / Nintendo Switch / iOS
  • 类型:RPG,生存,策略,回合制,克苏鲁题材,Roguelike
  • 时长:∞

剧情简介:

《暗黑地牢》 是一款 Roguelike 风格的永久死亡玩法的回合制 RPG 游戏。游戏中,一位贵族领主在挖地时不幸挖出一个不断吐出怪物的传送门,引发了一连串惨剧,而作为领主的远亲,恢复家族领土与名誉的担子就这么落到玩家身上,为此,必须招募各色英雄,组成冒险小队,对潜藏着恐怖怪兽的地下城进行扫荡。


有很多职业可供选择,而且随机事件也特别真实有意思,普通难度就已经很大了,但是这个游戏正因为有难度所以才好玩~

游戏中,玩家要招募大量千奇百怪的英雄,管理他们的怪癖和能力,然后搭配组团,扫荡各种充满恐怖怪物的地下城。之所以“与众不同”,是因为它刻意地不将设计重心放在英雄的成长、升级上,玩家要面对的,是英雄们沾染到的疾病怪癖,还有最重要的,那个随时会让他们发疯甚至致死的压力系统。简单来说,在这个游戏中,首当其冲的敌人不是那些面目狰狞的怪物,而是每位角色自己的心魔。

PS. 后期的游戏体验不是特别好,iOS 版本看评论据说很多 BUG。虽然 Switch 版本比 Steam 贵上许多,但是这类游戏,肯定要在掌机上玩才有意思呀~

5. Lost Castle

  • 中文名:失落地堡
  • 平台:Steam
  • 类型:动作冒险,横版,Roguelike,RPG
  • 时长:1~2 小时(单局)

剧情简介:不重要


这个游戏也是要和好友联机才有意思,以前和朋友经常玩,而且不会沉迷,毕竟一局才一小时~ 虽说是地下城游戏,但莫名觉得画风有点可爱… 据说这个游戏是华南理工大学的一个宿舍的在校学生做出来的,真的超级棒!

6. The Stanley Parable

做了选择,就选择了自由。

  • 中文名:史丹利的寓言
  • 平台:Steam
  • 类型:第一人称,剧情,多结局,冒险
  • 主题:哲学,人生,自由
  • 获奖:很多

剧情简介:

通过对旁白的分析,主角活在一个虚拟的网络中,思维被控制,现实生活里每天过着重复的生活,公司家里,2点一线,思维却一直在系统中。

旁白操纵着这一切,也许是为了科学实验,或者其他不可告人的目的,或许仅仅是个游戏。


本游戏没有任何难度,纯粹的故事,但一共有22个结局。这个游戏又不只是游戏,它的意义已经超出了它的本身。漂亮的逻辑和引导,当你觉得旁白在和“你”沟通的时候,你已经融入了角色,旁白打破了这第四面墙,或者说你的身上已经被发掘出了“史丹利”的实质。

这个游戏没有胜利结局,就像人的一生,总想摆脱命运的束缚,却终有一死。我们从一生下来开始就在不断逃避所谓的事实,但到了最后往往又回到了原点。游戏所有的结局都是正确的、自由的,因为这是我们自己的选择,就像游戏里旁白说的“做了选择,就选择了自由。”无论你是在开始的时候反锁房门草草结束自己的“一生”,还是在旁白的诱惑下连玩四小时的无聊婴儿游戏,这都是你的选择。当“你”离开房间的那一刻,你就开始了你的“一生”。

除了这些,游戏中值得我们思考的部分还有很多,这里就不剧透了,希望感兴趣的同学可以静心玩一玩这款不一般的“游戏”。

7. 塞尔达传说: 旷野之息

自由之于人类,正如任天堂之于游戏。

  • 英文名:The Legend of Zelda: Breath of the Wild
  • 平台:Nintendo Switch
  • 类型:开放世界,动作冒险
  • 时长:200 小时(不含 DLC)
  • 获奖:无数,IGN、GameSpot、Polygon全部10分满分,109家媒体 MC 平均分达97分,位列历史第三。

剧情简介:

曾经一场大灾难袭击了海拉尔王国使之灭亡,海拉尔王国灭亡的100年后,林克在地下遗迹苏醒,追寻着不可思议的声音,开始了新的冒险之旅。


这部必须吹爆,这毋庸置疑是 NS 上最好的一款游戏,没有之一,甚至个人以为,这也是最好玩的一款开放世界游戏。游戏非常自由、真实,风景很美,可玩性 max,内容非常丰富,交互性和细节用心到令人发指的地步,操作友好,新手也很容易上手。游戏不间断地带来的惊喜会让你更加有欲望去探索这个神奇的世界!

总之,自由、随意和探索才是《荒野之息》最为核心的理念和乐趣。在这款游戏里,你很少能发现一件自己想做却做不到的事情。

8. Hollow Knight

空洞骑士天下第一!(塞尔达是天)

  • 中文名:空洞骑士
  • 平台:Nintendo Switch / Steam
  • 类型:动画风格,类银河恶魔城,动作游戏
  • 时长:80 小时(不含 DLC)

剧情简介:

本作讲述的是一个名为德特茅斯的衰落小镇下掩埋着一个古老的废弃王国。许多人被这个王国所吸引,开启冒险之旅,寻找财富、荣耀、或古老秘密的答案。


这是一个由三个人开发的独立游戏,以昆虫为主体,构建了一个完成度令人难以置信的庞大世界,完成度可谓之极高,非常良心非常细心的一个作品,玩上手之后会发现作者真的很用心去做了这款产品,然后会产生疑惑,为什么其他游戏没有这么好的质量呢?游戏中每个场景的层次感和复杂程度都达到了令人惊叹的地步,画风很清新,剧情不错,设定新颖,音乐也超级棒,很多细节和彩蛋,最重要的是价格很良心,仅仅一次付费购买之后后续 dlc 全部免费,且 ns 版就¥30左右,而同等质量的游戏却要¥300多,可谓是一股清流,甚至 Steam 上很多评论要求开发者涨价…

游戏中的每个 NPC 都很“真实”,上一个带给我这种感觉的还是 undertale。一个是老鹿角虫,即使老也愿意继续工作,直到最后的时光; 一个是奎诺 留下剑在湖边人不见了,如果陪伴他会有一个最后的时光的成就;还有钉子匠,打造最高级的武器之后会有一个委托,他想体验自己打造的武器有多锋利,请你动手……

另外,这个游戏所带来的成就感是其他游戏无法比拟的,它不像塞尔达、奥德赛等传统游戏去“讨好”玩家,特意降低游戏难度;也不像奇怪的大冒险那样去“戏弄”玩家、“折磨”玩家。网上评价普遍说这个游戏劝退了很多手残玩家,但个人觉得其实还好,努力一下肯定能过,难一点的 boss 打个 10 遍肯定能过,一般的 2-3 遍即可。打过 boss 之后,玩家会得到无法比拟的成就感,而且技术得到了明显的提升,这或许就是游戏吧。

注:NS 版用手柄玩后期不是很友好,剑技蓄力之前必然会触发攻击影响流畅度,同时准备剑技之时不方便跳跃,这就导致了竞技场第二个 boss 空战会很难打,其他情况下没有影响。

本周记录

Recent Viewings

  • 看过:电影 |《心灵捕手》| ★★★★★
  • 在读:小说 |《沉默的巡游》
  • 在看:动漫 |《夏日重现》
  • 在看:动漫 |《间谍过家家》
  • 玩过:Switch |《宝可梦 · 盾》

《心灵捕手》值得后续单独写篇文章谈谈。

Recent Code

TypeScript React  35 hrs 27 mins ██████████████▏░░░░░░  67.7%
TypeScript        14 hrs 46 mins █████▉░░░░░░░░░░░░░░░  28.2%
YAML              41 mins        ▎░░░░░░░░░░░░░░░░░░░░   1.3%
Bash              21 mins        ▏░░░░░░░░░░░░░░░░░░░░   0.7%
JSON              20 mins        ▏░░░░░░░░░░░░░░░░░░░░   0.7%

周刊(第9期):高效率到高消耗的现象与反思

高效率到高消耗的现象与反思

现象:信息汲取的高效率到高消耗

之前在 腾讯研究院 看到了一篇文章 《互联网为什么让我们越来越不开心?》,文中提到了一个观点——移动互联网从高效率逐渐走向了高消耗。“上网”这件事本身,变得焦虑了。

今天,互联网成为我们的精神居所,成为我们与世界连接的媒介,我们处在一个“连接社会”,“人”与“物”之间的关系、乃至是人与人之间的“关系”,都被“连接”所取代。

每一个具体的、有丰富面向的人,沦为了抽象而单薄的数据收发节点,被视为透明得没有情感的流量。——《2022 大社交趋势观察报告》

回想一下,我们放下手机或关闭电脑的时候,心情是否都是满足、平静、愉悦的?我们上网的本初目的是为了获取信息、学习新知识,因此我们追求高效率的学习方法、想方设法屏蔽垃圾信息的干扰、寻找一个又一个高效的工具。

一面是试图去高效组织信息,但另一面却是让人不能沉下心,沉迷于“高效”的漩涡无法抽离。

我们总是想要“快”一点,追求把事情高效地做完——吃饭快一点、走路快一点、生活节奏再快一点,被时间推着走,越来越快。在这个时候,会发现“高效”本身成了一件高心理消耗的事情。

那为什么我们要越来越快?为什么追求高效变成了一场消耗?

原因:追求效率途中的自我阉割

《人生不是一场马拉松》 一文中提到了“自我阉割”的概念:

现代人的人生赛道就只有一条,就是财富的成功,所有人都患有成功焦虑症,中国人把人生简化成了一件事,就是寻找一种传说中的圣杯,名曰“财务自由”,一旦找到了这种圣物,就意味着人生通关了。在没有获得财富的成功之前,他们认定自己不配拥有婚姻和孩子。就像玩一场游戏,通关之后才会得到奖励,既然没有通关,那自然不配得到奖励,于是他们选择了自我阉割。——《人生不是一场马拉松》

追求效率的更深层次的原因是我们对于财富的追求。当然,每个人的目标其实并不一样,这个“财富”不仅仅是钱,也可以是被认可的感觉。

在茫茫大海中,我们不断地为这场人生的航行锚定航标。这种锚定是我们给自身设定的「航行规则」,我们通过这套规则抵达航标,去寻求那所谓的 「意义感」。

而我们希望尽可能快地去抵达航标,因此在抵达之前,只顾埋头向前,盲目追求高效,只希望让发动机尽可能变得再快更快。

而不断地,在这快节奏的生活中,丢掉了眼前的风景。快得来不及感受,快得来不及思考,快得来不及珍惜。

没有能量的输入,效率变成了消耗。

反思:人需要的不只是高效率

首先,效率背后的不是工具,而是人。

要时刻记得,追求效率的本质是想更好地做一件事情,而不是一天换一个 GTD 软件,却总是说忘记做事情;用着最贵的笔记软件,却总是拖延懒惰几天不写一个字。

《我是如何艰难地克服「效率成瘾」的?》 一文中将这种现象,称之为“效率成瘾”,并提供了五种解决方案。更详细的读者可以自己去阅读原文,这里只介绍其中我感受比较深刻的几个点:

  1. 破除工具的执念:能够区分玩具属性和工具属性,该用的时候就好好用,发挥工具的机制。
  2. 回顾工具对自己的价值:尽可能多的去使用工具,并利用它们做好输入输出的统计,回顾反思工具是否真的对自己产生了价值。
  3. 重器轻用:用自己觉得舒适的方式去使用工具,工具的目的是产生价值,而不是为了用而用。

其次,去做自己喜欢的事情。

在追求高效前,要搞清楚热爱的事情,再释放自己的效率能量。别让无谓的琐事,迷失了原本的目标。

高效本身不是目的,但追求高效可以让我们获得更多有价值的事物。追求高效,是为了节省出更多的时间,让我们能够有时间去做自己喜欢的事情、去体验生活、关注自己的身体与娱乐、去感受幸福。

最后,慢下来,去感受。

《效率的背后不是工具,而是人》 中有一段话说的挺好:

效率的人生,不是指每一件事情都高效完成,高效地沟通、高效地睡觉、高效地吃饭,这结果听起来就是高效地去死一样。

因此,要适当慢下来,阶段性地把注意力从手头上在做的事情转移开来,把焦点放在自己身上,沉静下来反思。

人需要的不只是高效率,我们要追求的也不是“快”,而是“慢”;我们缺的其实也并不是效率,而是舍弃。

慢下来,去关注自己的思维、情感和身体,好好去品味生活、体验生活,而不是把自己活成一颗螺丝钉。在之前的一篇文章 《谈谈存在的价值与人生体验》 中也不断强调过体验的重要性,这里不再赘述了。

那么要如何体验世界呢?

在 L 先生上周的文章 《停下来,去感受》 中提到了要培养自己的感知力。感知力能把自己跟他人、跟世界连接起来,形成一个共同呼吸的整体。它包括以下三个部分组成:

  • 自我监控:能够敏锐地感受到自己身体内在的细微变化,体会到自己的状态。
  • 他人共情:能够切身体会到别人的心情、想法、感受,站在别人的立场考虑。
  • 外部沉浸:能够自主地将注意力集中在外部的环境中,体验和感受外部世界。

“爱具体的人,不要爱抽象的人;爱生活,不要爱生活的意义。”——陀思妥耶夫斯基

高效只是一种个人的选择,只是迈向人生目标的一种手段而已。而同时,也没有人规定过人生是一场马拉松。

当年明月在《明朝那些事儿》的最后一章,通过徐霞客的故事 表达了足以藐视所有王侯将相,最完美的结束语——“成功只有一个——按照自己喜欢的方式,去度过人生”。

此前,我讲过很多东西,很多兴衰起落、很多王侯将相、很多无奈更替、很多风云变幻,但这件东西,我认为,是最重要的。因为我要告诉你,所谓千秋霸业,万古流芳,以及一切的一切,只是粪土。先变成粪,再变成土。——当年明月

毕竟,一切都是外在之物,唯有健康、梦想、记忆和爱,才会伴随我们终生。

最后,愿你不随波逐流,目有可及之爱,心有满溢光彩。

每周推荐

《2022 大社交趋势观察报告》

PDF 报告原文:2022大社交趋势观察报告-袤则咨询

很喜欢这种用研报告的呈现形式和内容结构,每个观察点都包括以下四点组成:

  • What's new
  • Case
  • Why
  • How

Muse 2.0

Muse 前段时间出了 2.0 版本,登录了 Mac。不过,还是期待一波 iPad OS 的新系统出的白板应用。

PS. Muse 的开发者很 Nice,针对国行特定调低了价格。除此之外,我之前用蹩脚英语给他们发邮件咨询文件同步的功能,他们也很认真地回复了。体验了之后会发现,Muse 着实是一款用心打磨过的好产品。

扩展阅读:设计、体验都出彩,白板工具的又一选择:Muse - 少数派

本周记录

Recent Viewings

  • 读完:情感 |《见字如面》| ★★★☆☆
  • 看过:悬疑 | 《杀人回忆》 | ★★★★★
  • 在看:动漫 |《间谍过家家》
  • 在看:纪录片 |《英雄之路》
  • 在玩:Switch |《十三机兵防卫圈》

Recent Code

TypeScriptReact  32 hrs 30 mins ███████████████▋░░░░░  74.5%
TypeScript       7 hrs 31 mins  ███▌░░░░░░░░░░░░░░░░░  17.2%
JavaScript       1 hr 35 mins   ▊░░░░░░░░░░░░░░░░░░░░   3.7%
JSON             42 mins        ▎░░░░░░░░░░░░░░░░░░░░   1.6%
Dart             20 mins        ▏░░░░░░░░░░░░░░░░░░░░   0.8%

周刊(第8期):三幕剧与英雄旅程

三幕剧与英雄旅程

说三幕剧之前,我们先谈谈如何分析一个故事的组成,以前语文课程学过,总结下有两种方式:

  1. 要素拆解法
  2. 叙事过程拆解法

所谓要素拆解法,就是分析「人事时地因」(5W),如果要辅以叙事方法的分析可以再加上「如何叙事」 这个要素,以组成 5W1H:

  • 人物(Who)
  • 事件(What)
  • 时间(When)
  • 地点(Where)
  • 为何发生(Why):故事发生的背景原因
  • 如何叙事(How)

而所谓叙事过程拆解法,常见的是「起承转合」:

  • 起:故事的起始,如何开始,事件的触发等等。
  • 承:故事的延展,剧情的铺陈与持续。
  • 转:故事的高潮,转变的剧情,预料之外的发展。
  • 合:故事的结局。

「起承转合」这种讲故事的方法与亚里士多德阐释的“故事基本要素”(即开端、中段和结尾)一脉相承。而三幕剧结构亦是如此。

经典的三幕式结构又叫戏剧性结构、冲突性结构,将故事分为三个部分:

  1. 开始(Setup):又称触发或启程,是三幕剧的第一个部分。是故事的开始和原点, 通常用着将观众带入故事的作用,用来解释故事的设定和背景,以便接下来冲突发展。
  2. 对抗(Confrontation):又称冲突或转折,是三幕剧的第二个部分。是故事中角色面临难关,或是重大打击的时刻,第二幕有着将 角色心境推到低谷 以及 强化戏剧张力 的作用。
  3. 解决(Resolution):又称结尾,是故事的终结,是三幕剧的最后一部分。通常会处理第二幕发生的冲突,无论是克服难关, 或是跨过自身的障碍门槛。第三幕需要做到 拉抬角色心境故事高潮 以及 收尾 的作用。

上图取自维基百科的三幕剧时间轴图,将每幕(Act)链接起来的就是情节节点(Plot),而三个幕的代表点位分别是触发事件(Inciting incident),中点(Midpoint)和高潮(Climax)。

值得一提的是,在写故事的时候,一个故事不一定只能有一个三幕剧结构,在叙事过程中,将数个三幕剧剧情混在一起,如:

触发 → 冲突 → 解决 → 触发 → 冲突 → 解决 → ……

举个例子,《僵尸世界大战》就是这样一部剧,全程无尿点的原因之一就是故事节奏紧凑,由数个三幕剧的副本构成的电影。所以在 B 站看的时候,特别有在玩《求生之路》的既视感,弹幕也特别沉浸(“任务完成”,“第一章结束”):

观察这部剧的弹幕热度条会发现一个有意思的现象:

图中每个章节是我自己起的,根据剧情大概可以分成 6个三幕剧,其中每一章中恰会有一个弹幕峰值,这个峰值对应的恰是这一幕故事的高潮情节:

  • 点 1:第一幕的高潮,大街上爆发丧尸
  • 点 2:第二幕的高潮,主角团黎明前夕在大楼中逃生
  • 点 3:第三幕的高潮,主角团在朝鲜机场爆发枪战
  • 点 4:第四幕的高潮,主角团在以色列遭遇丧尸群突破救世之墙
  • 点 5:第五幕的高潮
  • 点 6:第六幕的高潮,不是特别明显,

除了三幕剧本身可以叠加,为了突出剧情的连贯性,有时也可将三结构改为五结构:

触发→ 冲突 → 解决冲突 → 冲突 → 解决冲突

英雄旅程(Hero’s Journey)是好莱坞编剧提出的「英雄旅程的 12 个阶段」,其是基于神话与常见剧本所整理出的一套英雄主角成长路线的模版:

  1. 平凡世界:代表主角的背景和出身,向观众介绍英雄的出场,营造认同感。
  2. 冒险的召唤:要求主角上路的各种形式。
  3. 拒绝召唤:凸显旅程的危险和代价,无论主角怎么拒绝最后都还是会上路。
  4. 遇上师傅
  5. 跨越第一道门槛:第一幕的结尾,英雄来到两个世界的边界,开始上路,故事真正开始。
  6. 试炼、盟友、敌人:进入非常世界,英雄开始迎接试炼、结交盟友或树立敌人,三者顺序不拘。敌人也经常以竞争对手取代。
  7. 进逼洞穴最深处:即将抵达非常世界的核心,通常更加神秘,展示第二道门槛。
  8. 苦难折磨:故事核心,主角重生前夕的难关。
  9. 奖赏(掌握宝剑):死里逃生的主角获得报酬,换得在非常世界中追寻的某种东西。
  10. 归返之路:第三幕开头,主角可能继续上路或者回到平凡世界。
  11. 复苏:故事高潮,对抗最后一个 Boss。
  12. 带着万灵丹归返:故事结局,主角带着战利品从非常世界回到平凡世界。若是开放式结局,会让情节继续。

除此之外,英雄旅程在早期也有其他变体:

但无论何种变体,都满足三段结构:启程→启蒙→归返。

这套模版是非常常见的,如《功夫熊猫》就是经典的英雄旅程。

然而,大多数时候硬套模版只会荒谬地陷入陈腔滥调。

其实情节并非就是教条式的“开端、发展、高潮、结局”,许多优秀的文章和剧集代之以“生活的横断面”结构。最明显的是,开端显得非常不重要,往往是从事件的当中讲起,开端退化为后来的某种不着痕迹的交代,更不在乎严格意义上的结尾。

比如《看不见的客人》,就没有严格意义上的“开端”,情节一环扣一环,将背景潜入故事之中,不断勾起观众好奇,使之沉浸于剧情。

再比如《孔乙己》,小说中没有交代孔乙己的身份背景,而是将他的故事全都放在场景的交代中去,只写酒店的三个场景,其中一个孔乙己还没有出场。

优秀的故事没有永恒的公式,《故事》中说道:“故事是生活的比喻。”它的内核来自于创作者对生活的信念,是创作者思想和激情的体现。作品中的每一个情节、每一个瞬间,都必须充盈着富于激情的信念。

电影就是将精神的东西物化。——约翰·卡彭特

所以陈词滥调的根源并不是硬套模版,反而熟练掌握经典叙事模版是每个创作者的基础功力。

一切陈词滥调的根源可以追溯到一个原因,而且也是唯一的原因——创作者对笔下故事的世界没有深入的了解与洞察。

要知道,叙事形式不是最重要的,它只是创作者叙事故事的一个载体,最重要的是故事的内核。

每周推荐

本周推荐一些好用的笔记软件。

Obsidian

经典的卡片盒式笔记工具,目前我用作 Markdown 写作工具。

LogSeq

大纲式双链知识库笔记工具,是 Roam Research 的平替版(甚至做的更好),也是我目前唯一在使用的笔记工具。

Hepta

作者是一个台湾刚毕业的大学生,对笔记有一套自己的思考,而且他能做到 Hepta 几乎日更,这也是我很佩服的一个点。

本周记录

Recent Viewings

  • 在读:情感 |《见字如面》
  • 在看:动漫 |《夏日重现》
  • 在看:动漫 |《间谍过家家》
  • 看过:电影 |《隧道》(二刷)
  • 看过:电影 |《奇异博士 2》
  • 看过:游戏实况 |《采石场惊魂》
  • 玩过:Switch |《你剪我裁》
  • 玩过:Switch |《糖豆人》

因为自己很怂,但又很好奇恐怖游戏的剧情,所以很喜欢看别人玩恐怖游戏,还有弹幕护体,一点也不慌。

By the way,糖豆人免费了 & 登录了 Switch,直连游玩体验 ok,一定要尝试下!(游戏就应该是这种纯粹的快乐呀)

Recent Code

TypeScript React    40 hrs 12 mins ██████████████▋░░░░░░ 70.1%
TypeScript          13 hrs 43 mins █████░░░░░░░░░░░░░░░░ 23.9%
JSON                1 hr 2 mins    ▍░░░░░░░░░░░░░░░░░░░░  1.8%
JavaScript          41 mins        ▎░░░░░░░░░░░░░░░░░░░░  1.2%
HTML                38 mins        ▏░░░░░░░░░░░░░░░░░░░░  1.1%

周刊(第7期):即兴发言模型

即兴发言模型

这周刷 B 站看到了一个视频,Up 主介绍了一些发言模版,本文归纳总结一下。

  • Yes And 原则
  • 金字塔法则
  • FFC 赞美法则
  • FFA 汇报法则
  • NVC 技巧

Yes And 原则

适用场景:表达相左观点

核心思想:不要否定别人,以肯定代替否定

“Yes! And…” 技巧源于意大利的即兴喜剧, Yes 代表同意对方说的话,And 是在 Yes 的基础上添加自己的观点。因为即兴表演是没有剧本的,因此想要顺利推进情节就需要承接对方的话,然后再添加自己的内容,这样整体的节奏会更加连贯。若是相互否定,就相当于砍掉了观众前文接受的内容,拼凑的情节无法高效连接。

那么应用于生活之中,即便有人提出自己不认同的观点,请先不要着急否定,即不要着急说“But”。而且是从 “Yes” 的出发点出发,先说自己认同的点,表达肯定、接纳、包容对方的态度,之后再通过 “And” 把自己的其他想法说出去。

A:我觉得这个方案 XXXXX,你看我这个想法怎么样? B:嗯,我觉得挺好的呀(YES),并且(AND)我这里还有一个思路,你看看怎么样?

And 和 But 都能表达出自己的观点,但否定容易激起对抗情绪。而在“求同存异“的沟通氛围下,更容易达成共识,也更容易激发新的信息。

金字塔法则

适用场景:需要吸引受众注意力或高效沟通的场景。如答辩、汇报、写作等。

核心思想:金字塔原理。

  • 自上而下表达,结论先行
  • 自下而上思考,总结概括
  • 纵向总结概括
  • 横向论点分组

优势:

  • 吸引受众注意力
  • 提升受众的信息接收效率,避免浪费时间

FFC 赞美法则

适用场景:赞美他人

FFC 赞美法则指在赞美一个人的时候,先用细腻的语言来表达自己的感受(Feeling),然后再进一步通过陈述事实(Facts)来证明自己的感受并非空穴来风,最后再通过一番比较(Compare),来表达对对方的深度认同。

我觉得你今天穿的这件连衣裙真的很有品味、很有范儿啊(Feeling)!因为这件连衣裙的款式和你的气质很搭,同时也非常适合你的肤色和身材(Facts)!说实话,这是我今年入夏以来,看到最好看的一件连衣裙了。(Compare)

注意:

  1. 不要赞美事实上没有的特质,否则不真诚
  2. 不要和在场的其他人做比较,比较范围要拉大

FFA 汇报法则

适用场景:职场汇报

核心思想:

  • 客观陈述事实 Facts
  • 表达自己的看法 Feeling
  • 给出行动方案 Action

X 总,这个月销售额下滑了 30% (Facts),我觉得可能是新人的业务扩展能力有些问题 (Feelings),下周我会再组织一次业务培训解决一下这方面的问题 (Actions)。

NVC 技巧

适用场景:亲密关系

Nonviolent Communication 非暴力沟通法则,在《非暴力沟通》中的「爱的语言沟通四要素」中有详细介绍,此处不再赘述。

核心思想: 爱的语言沟通四要素

  • 观察:描述观察
  • 感受:表达感受
  • 需要:让对方感受到需求
  • 请求:提出要求

每周推荐

漫画/动漫:夏日重现

比起 re0 486 的恋爱轮回,这一部的主角简直是炼狱难度,以凡人之躯对抗超自然生物。

和《开端》相反,主角轮回有极大的限制,《开端》是每次死亡轮回点会提前,而本作则是延后,因此某些发生的事情已成定局无法改变。本作的反派方亦有着xx(剧透打码)能力,利用情报差压制主角团,甚至一度堵在主角的出生点,令人感到压抑绝望。

本作主角团和反派智商在线,剧情环环相扣,设定严谨,所有的伏笔都有回收,堪称今年的神作。

编程:现代 JavaScript

偶然间发现的一个免费的学习前端的网站,网站设计精美,并且知识内容的深度和广度皆有,值得新人学习,也值得老人巩固基础。

本周记录

Recent Viewings

  • 在读:情感 |《见字如面》
  • 在读:哲学 |《存在主义咖啡馆:自由、存在和杏子鸡尾酒》
  • 在看:动漫 |《夏日重现》
  • 在看:动漫 |《间谍过家家》
  • 看完:漫画 |《夏日重现》(三刷)
  • 看过:电影 |《绿皮书》(二刷)
  • 玩过:单机 |《黎明前 20 分钟》

《黎明前 20 分钟》是一款张弛有度的小品级 Rougelike。Steam 仅售 15 块,10~20 分钟快速游玩一局,在游戏中寻找适合自己的战斗风格是这款游戏的主要乐趣。

PS. 想云游戏的玩家可以看看王老菊的视频 → 王老菊教你黑夜打枪_哔哩哔哩_bilibili

Recent Code

TypeScript React  32 hrs 23 mins █████████████░░░░░░░░  61.9%
TypeScript        18 hrs 50 mins ███████▌░░░░░░░░░░░░░  36.0%
JSON              32 mins        ▏░░░░░░░░░░░░░░░░░░░░   1.0%
JavaScript        10 mins        ░░░░░░░░░░░░░░░░░░░░░   0.3%
Markdown          6 mins         ░░░░░░░░░░░░░░░░░░░░░   0.2%

周刊(第6期):沟通艺术中的知觉检核

沟通艺术中的知觉检核

这周看到一篇文章总结了 《沟通的艺术》 的一些观点,本期周刊重点谈谈文中的「知觉」。

不同的人给这个世界赋予了属于自己的意义,而人类给身处的世界赋予意义的能力叫做「知觉」。因此,同一件事情,在历经过不同的人的知觉过程后,所被赋予的意义都是不同的,这就导致了我们往往不能准确的理解他人,为人与人的之间沟通赋予了无数的噪声。

文中介绍了知觉的历程:选择—组织—诠释—协商,这四个流程非常类似于去年在《如何有效沟通》一文中介绍的推理阶梯,这里不再赘述。

接下来看看《沟通的艺术》中介绍的「交流沟通模型」,由图可见:

  1. 消息的发送者与接受者处于不同但又有部分重叠的背景中,不重叠的地方称之为代沟,是沟通者双方所独有的背景,包括文化背景,家庭背景,教育背景等因素所产生的差异。
  2. 沟通是双向的,没有纯粹的发送或接受者,即便是倾听,也会专注思考或者微微点头、时而皱眉,或者其他的肢体语言也在给对方传递消息。
  3. 噪声充斥于整个模型之中,不仅仅是沟通者背景上存在噪声,消息传递的渠道、编解码也会存在噪声,与语气语速的不同都会导致理解偏差;除此之外,沟通者自身也存在噪声,这就是知觉差异导致的。

而所谓知觉检核,就是通过尽可能消除知觉差异产生的噪声来增强个人同理心,从而尽可能去理解对方。

《如何有效沟通》中我介绍了固有立场的概念,那知觉检核就是去 Check 所有可能立场,从不同立场出发去思考,从而准确的了解对方所要表达含义。所有立场不外乎以下五点:

文中还介绍了知觉检核的步骤:

  • 第一步:描述自己注意到对方的行为
  • 第二步:列出至少两个自己对于此行为的诠释
  • 第三步:请求对方澄清你的诠释

需要注意的是,这里的知觉检核的目的是为了 「准确地了解对方」 。而《非暴力沟通》中所提到的「爱的语言沟通四要素」(观察、感受、需要、请求),它的目的是 「让对方理解自己」。沟通是双向的,《沟通的艺术》在这里补足了《非暴力沟通》所缺少的那一环节。

知觉检核还有一个作用,就是在沟通中尽可能摒除情绪作用产生的噪声。

在《认知觉醒》中介绍了人类的三重大脑:理智脑、情绪脑和本能脑。

所有情绪在本质上都是某种行动的驱动力,即进化过程赋予人类处理各种状况的及时计划。—— 《情商1》

本能脑和情绪脑运算速度极快,至少可达 11000000 次/秒,而理智脑的最快运行速度仅为 40 次/秒。所以,综合来看,理智脑对大脑的控制能力很弱,因此,我们在工作和生活中,大部分做决策往往源于本能和情绪,而非理智,这就导致了沟通极大地可能受到情绪的影响。

能控制好自己情绪的人,比能拿下一座城池的将军更伟大。—— 拿破仑

可以笼统的说,我们的情绪更像是我们想法的结果,而非我们所遭遇的事件的结果。而我们给事物赋予不同的意义又会给我们带来不同的感觉,也就是不同的情绪。

时常做知觉检核可以更好地审视自己,也能够让自己了解到,沟通既是一种谈话技巧,更是一种思维方式,更侧重于挖掘语言背后双方所蕴含的情感与期望。当我们专注于彼此的观察、感受及需要,而不反驳他人,我们便能发现内心的柔情,对自己和他人产生全新的体会。

这就是非暴力沟通,这就是沟通的艺术。

扩展阅读:

每周推荐

优化输入法体验:Input Source Pro

Input Source Pro 可以根据应用或是网站自动切换输入法,并且在切换窗口的时候还会贴心的提示当前的输入法是什么,比如设置 VSCode、iTerm、Xcode 默认为英文输入法,而笔记软件和企业微信默认为中文输入法,切换软件时再也不需要按 shift 键了!(这款软件安利给同事们之后纷纷点赞,简直程序员刚需)

渐进式单词学习插件

Relingo 是一款免费的 Chrome 插件,可以自动标注网页里的生词,你可以根据自己的掌握程度来选择性地将它们一键加入到生词本中,通过日常的网页浏览,渐进式地学习英语单词。

截图软件

Snipaste 是我一直在用的免费截图软件,Windows 和 MacOS 都支持,最喜欢的是可以把截图贴在桌面上的能力,如同便签一样,方便我贴重构稿、关键信息、对比代码、二维码等信息到桌面上:

Shottr 是最近新出的又一款 MacOS 平台的截图 App,功能强大、界面美观、而且免费,较于 Snipaste 多了滚动截图、窗口截图、OCR 等能力,但是缺少了我最喜欢的桌面贴图功能,而且个人体验下来,虽然它很美观,但是标注图片时总是不如 Snipaste 用的顺手。

CleanShot X 是一款老牌 & 功能强大的 MacOS 平台的截图软件,以上两款软件的功能它都有,除此之外,还有录制视频、录制 GIF、上传图片到云端、顺序标注等独有的能力,界面也很美观,和 Raycast 的联动做的也很好,还有独创的 「all in one」 模式。唯一的缺点是需要付费(现在 618 大促,仅需 ¥104 元/设备)。

本周记录

Recent Viewings

  • 读完:小说 |《红手指》| ★★★★
  • 在读:哲学 |《存在主义咖啡馆:自由、存在和杏子鸡尾酒》
  • 在看:动漫 |《夏日重现》
  • 在看:动漫 |《间谍过家家》
  • 看完:电影 |《这个杀手不太冷》| ★★★★
  • 玩过:游戏 |《原神》2.7 危途伏事 | ★★★★
  • 玩过:游戏 |《Shotgun King》| ★★★★
  • 在玩:游戏 |《胡闹厨房 2》

Shotgun King 是一款很有创意的游戏,魔改了国际象棋,你是黑棋国王,虽孤军奋战,但你有一把霰弹枪。

Recent Code

TypeScript React   45 hrs 11 mins ███████████████▊░░░░░  75.3%
TypeScript         10 hrs 14 mins ███▌░░░░░░░░░░░░░░░░░  17.1%
Markdown           1 hr 50 mins   ▋░░░░░░░░░░░░░░░░░░░░   3.1%
JavaScript         1 hr 17 mins   ▍░░░░░░░░░░░░░░░░░░░░   2.2%
JSON               1 hr 1 min     ▎░░░░░░░░░░░░░░░░░░░░   1.7%

周刊(第5期):我在 TME 工作的一天

我在 TME 工作的一天

  • 7:30,起床
  • 8:10,出门,坐班车上班
  • 8:50-9:30,到公司;早饭时间 & 看阅读清单里的文章
  • 9:30-10:00,整理昨天遗留的与今天要做的事情,开始工作
  • 10:00,晨会时间;会后开始上午的工作
  • 12:00,午饭时间
  • 12:30~13:30,看阅读清单里文章 or 看书
  • 13:30~14:15,午睡;午休后开始下午的工作
  • 18:00,晚饭时间
  • 19:00-21:00,晚上编码时间,直至下班
  • 22:00-23:30,娱乐时间
  • 23:30-0:00,睡前看书时间,远离蓝光设备,准备入睡

去班车点的路: 去班车点的路

我的工位: 我的工位

我的书桌: 我的书桌

每周推荐

技术:Why’s THE Design

这周看到了个不错的博客网站:为什么这么设计系列文章 - 面向信仰编程,博主撰写了一系列关于计算机领域中程序设计决策的文章,在这个系列的每一篇文章中他都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。这种提出问题并去深入研究的学习方法非常值得学习。

软件:1Password 8

作为 1Password 老用户,一直停留在 1Password 7 没有去主动升级上周发布的大版本,因为很久之前看新闻说新版本用 Electron 开发,评论区都在吐槽 Electron 的性能,对抛弃原生开发的做法表示非常不满。但我这周看到 1Password 8 宣传页,深深被新 UI 吸引了,“不争气”地升级到了 8。

体验了几天下来发现,作为密码管理软件而言并没有什么要吃性能的地方,整体 Electron 的性能非常流畅。我想如果官方不主动对用户表示新版本换了技术栈,应该也不会有人发现新一代的 1Password 居然不是原生开发的。

这让我想到了前段时间看的一篇文章(很遗憾我找不到原文了),大意是说开发者把 iOS 原生开发的应用,在新版本中悄悄换成了套壳 PWA,但是用户们全都没有发现。

用户体验不单单只看软件的理论最优性能,而是要看系统整体交互设计、动画呈现、加载时序的合理性,真正从用户需求的角度出发,感受他们的真实体验和容忍度。然后在这个基础上,再去选择业务合适的、流程高效的技术栈。

PS:我组的 1Password 家庭车还有三个车位,有兴趣的童鞋可以找我加。

本周记录

本模块为本期起的新增模块,记录个人每周书影音和编程情况,是个人简短的小周报。对于看完的书籍电影会简单打上个人主观评分,而编程记录则读取自 Wakatime,GitHub Action 每周定时会拉一次数据,之后同步到个人 Gist 上。由于是 6.1 才启动的 Wakatime 插件,因此本期编程数据从 6.1 开始。

之后我会再抽空优化下 Weekly 的 CI,每次推周刊的时候由 CI 去抓豆瓣和 Gist 的记录后自动生成这个「本周记录」模块。

Recent Viewings

  • 读完:小说 |《占星术杀人魔法》| ★★★★★
  • 看完:电影 |《瞬息全宇宙》| ★★★★★
  • 看完:剧集 |《金宵大厦》| ★★★☆☆
  • 在读:小说 |《红手指》
  • 在看:动漫 |《夏日重现》
  • 在看:动漫 |《间谍过家家》
  • 在玩:游戏 |《胡闹厨房 2》
  • 在玩:游戏 |《星际拓荒》

Recent Code (6.1-6.2)

TypeScript React   11 hrs 3 mins  ██████████████▌░░░░░░  69.5%
TypeScript         4 hrs 45 mins  ██████▎░░░░░░░░░░░░░░  30.0%
Objective-C        3 mins         ░░░░░░░░░░░░░░░░░░░░░   0.3%
Bash               0 secs         ░░░░░░░░░░░░░░░░░░░░░   0.1%
Markdown           0 secs         ░░░░░░░░░░░░░░░░░░░░░   0.1%

周刊(第4期):个人博客演变史

个人博客演变史

熟悉我的朋友都知道我记录的会比说的多,很多人觉得言语比文字更有力量和情感,而我却不这么觉得。情感是真情的投入,和呈现载体无关。这篇周刊,我想谈谈我个人博客的演进记录。

第一阶段:Wordpress

2014 年上半年接触编程,之后想搭建个人网站来承载 QQ 空间上的文章,上小学中学时 QQ 空间上写了蛮多东西的。之后淘到了 ursb.me 这个域名,因为那会应该舍友之间经常会对骂“你是 s* 吗?”,结果一搜还真有这个域名。因此,这四个有含义字母组成的域名,对我来说弥足珍贵。

之后域名提交备案,然后搞了个“虚拟空间”搭建个人网站(对,当时叫虚拟空间,其实就是和别人同租一台服务器,用户只有某个目录的权限),第一版博客用的框架是 Wordpress,花了很多时间折腾了贼多插件,结果半年过去了文章一篇也没写。

Wordpress

第二阶段: Hexo

反思了一下不能这么搞,于是 2015 年由繁入简,改用 Hexo + GitHub Pages 去部署博客,那段时间基本上周更写了蛮多内容的。但折腾劲一点儿也没有消退,还是搞了很多网站分析和评论的插件,这个静态网页的秒开想必也好不到哪去。折腾了这么多,结果一看站点分析,其实根本没有人评论也没有人阅读,pv 少的可怜,那点 pv 还基本都是我自己点的。饶是如此,我还是坚持在更新,没有有人也没关系,重点在于记录。

2014-2016 的具体经历可以看 从 WordPress 到 Hexo:ursb.me——个人博客折腾笔记 (PS. 这篇文章已经 out 了,2017 年又改了技术方案)

第三阶段: Typecho

直到 2017 年,我一直用的评论插件 “多说” 停止服务了,之后转用难用的网易云评论,结果没两个月也停止服务了。如果用国外的服务,又会影响读者的评论。之后思来想去,找个了轻量级的动态框架 typecho 来搭建新博客——也就是现在的 Airing 的小屋

Airing 的小屋

老博客近百篇文章没有迁移到新博客,仅存留本地了。新博客目标是不再灌水,记录之上输出自己的思考,所以更新频率很慢,基本上 1-2 个月才有更新,上班之后更新的就更慢了,毕竟平时工作写的技术方案也不可能发表到博客上。

新博客技术文的评论区冷冷清清,但是 pv 其实和非技术文是差不多的、甚至更高,可能开发者都喜欢白嫖吧。有时候搜 Flutter 的问题,结果发现搜索结果中的第一篇文章就是自己写的,那种成就感还是挺满的。

第四阶段: Typecho + Hugo

但其实非技术文并不要求那么高的严谨性,如果控制发文的质量反而会减少产出,减少记录的频率,迷失记录本身的目的。因此前段时间开始启动周刊项目(具体可见 WJ.1: 开刊,为什么写周刊),也希望自己能用输出倒逼输入。因此用 Hugo 简单弄了个静态网页才存下周刊的文件,同时同步发下公众号:

Airing Weekly

前两天重构下的个人首页 Hi, folks | Airing,它同时指向了我新搭建的 Airing WeeklyAiring 的小屋

ursb.me

在搭建 Airing Weekly 的时候使用了 Hugo 和一些 PaaS,工作日晚上忘记了时间折腾到一点多,折腾的时候才发现原来 Web 前端 还是这么有意思。回想起来,这份折腾的初心也恰我当年决定选这个岗位就业的原因呀!

每周推荐

技术:Web 浏览器原理解密

四篇 Chrome 开发者官网上关于浏览器原理的讲解,图文并茂、深入浅出,值得学习。

技术:触发 Reflow/Repaint/Composite 的样式

上周偶然间看到的一个报告——《 CSS Triggers》,记录了各主流浏览器内核(Blink、Gecko、WebKit、EdgeHTML)会触发回流、重绘、合成的样式。

这里可以发现不同内核针对不同样式的处理是存在差异的:

小说:《恶意》

上周日二刷了《恶意》,即便是二刷仍然惊叹于其独特的写作手法与峰回路转的剧情发展。

在不剧透的前提小结一下写作手法的创新点:

  1. 书信式第一人称自述:融入大量细节与心理描写,代入感强。全程推理紧贴我们读者前文看到的内容,并在最后来一脚绝杀,以至于被绝杀之后仍沉浸于剧情中回味不穷。
  2. 交叉递进式的书信推动剧情:剧情推动依然靠书信的交替,交替时会承接上文文末的内容,所以并不突兀。然而每封书信的内容里皆在合理的前提下有着巨大的剧情转变,紧扣心弦,不断激发着读者的好奇心。
  3. 杀人不是目的,而是手段:聪明的读者可以很快推理出凶手,甚至于 1/4 篇幅时就抓住了凶手。但杀人动机的推理才是本篇的核心内容,先抓凶手再推理杀人动机,在推理小说中不常见,其中夹杂了嫌疑人的心机诡计,这让被害人的死亡并不是“结束”,而是“开始”。

纪实:《桶川跟踪狂杀人事件》

看之前未曾想到纪实文可以写的比电影还惊心动魄、剧情比小说还跌宕起伏,而恰恰是纪实文,对被害人的遭遇才更有共情、对现实的无奈更感愤懑;而正因为是纪实,我才更加钦佩于作者这样的记者,胸怀正义地投入事业,在黑暗的现实里永远追逐光芒。

番剧:《间谍过家家》

最近逢人就安利《Spy x Family》,剧中一家三口设定立体、各有萌点,三者身份的冲突,混合在一起之后奇迹般的爆发出魔法般的效果,轻松、诙谐,又带点小温暖。

超可爱

扩展阅读:【影评】间谍过家家:日常与非日常的二重奏

先行者(Pioneers)思想

1970 年代的先行者思想的三个要点:

  1. 放大想象力(Amplify Imagination) ——Alan Kay
  2. 增强智力(Augment Intellect)——Douglas Engelbart
  3. 让思想不限于载体(Expand our Thoughts far beyond text on paper) ——Ted Nelson

前两点也是我很看重的,而第三点我没有特别多的感觉,应该指的是要懂得从书本之外会获取知识。

如果要我写三点:

  1. 知识
  2. 敏感
  3. 想象力

周刊(第3期):花束般的恋爱

本期周刊原本准备的内容并非是这个,昨晚临时换了,决定通宵写这篇。这期周刊是迟来的“官宣”,也是“告别书”,以纪念我刚刚逝去的“花期”仅 20.5 天的爱情。

虽然认识的时间不长,在一起的时间也很短暂(5.1-5.21),但是很感谢你给予我的一切,这是我经历过的最甜美的感情,满足了我这些年来对爱情一切的幻想。此外,也很想对你补充句对不起,我没有呵护好这段情感让它夭折了,你决绝而又体面地转身离开,让它永远地停留在了最美的日子里。谢谢你。

这期周刊是迟来的《花束般的恋爱》的观后感,这是我们唯一一场,在电影院里看过的电影。你当时偏要看这部,我却说结局是 BE 不想看。但无论是当时看完、还是此时此刻,《花束般的恋爱》向我展示的那个爱情最甜美的模样,一直一直埋在我心底,成为我的憧憬。之所以一直没写这篇观后感,也是不好意思以文字的形式去表达爱意,也从来没有写过文章去讨论过情感。

而事已至此,往事已矣。今天假借周刊补上“官宣”,同时结合《花束般的恋爱》来谈谈我对爱情的理解。

麦和绢是两个极其合拍的人,在一次偶然的机会下两人相遇、相识、相知、相恋,他们度过了一两年极其甜蜜的时光。大学毕业之后,麦沦为社畜改变了很多,顶着升职加薪的压力一直向“钱”,不再记得《宝石之国》的剧情、《黄金神威》追到第七卷之后就没有再看了、不再和绢讨论今村夏子的新作,而绢一如学生时代的少女一般,可以辞掉体面的职业去追逐着自己喜欢的工作、看自己喜欢的话剧、追着小说和漫画。

节奏的不一致、价值观的分歧,最终导致感情在第五年时走到了终点。分别之际,麦向绢求婚。麦的逻辑是恋爱谈不下去了,就结婚吧;婚姻维持不下去了,生个孩子吧;孩子也不行的话,那就凑合着过吧。反正爱情会变成亲情,不都这样么?

确实如此呀,喧嚣嘈杂快餐式环境下的现代年轻人在一道一道程序里走过这一生,一次一次受挫之后一次一次地降低标准、不断妥协、一直让步,用一个黑洞堵住另外一个黑洞,直到自己也迷失在无边无尽的黑夜里。

而绢还是那个纯心依旧的女孩,对爱情有着憧憬的少女,哪怕对方是自己最爱的人,忍着泪也拒绝了求婚,决绝分手了。正印证了那句:“结束的恋爱是必然,不结束是偶然”。麦和绢无比合拍、令人羡慕的一对情侣,最终走向了 BE 结局。

这便是《花束般的恋爱》的整个故事。

接下来我将尝试回答四个问题:

  1. 爱情是什么?
  2. 为什么我们需要爱情?
  3. 爱情的纽带是什么?
  4. 失去爱情之后,要如何自处?

爱情是什么?

首先,尝试回答一个问题没有答案的问题——爱情是什么?

你一直抱怨是因为麦改变了,所以神仙情侣的爱情走向了终局。但我想说,麦没有错,绢也没有错,这就是现实。爱情本质上不过是荷尔蒙导致的愉悦情绪,这是短暂的、来得快去得也快,如果想要维持爱情,或许可以采用小别胜新欢、更强烈的荷尔蒙刺激等形式来短暂维持,但是最终的最终,它还是会消散。

在周刊第一期,我曾引用《重庆森林》里的一段话:“不知道从什么时候开始,在每个东西上面都有一个日子,秋刀鱼会过期,肉酱会过期,连保鲜纸都会过期。 我开始怀疑,在这个世界上,还有什么东西是不会过期的?”

没错,万事万物都是有保质期的,以科学的视角来看,爱情的保质期最长是 18 个月,最短的如你所言,是 21 天。而我们停留在 20.5 天,爱情还在的时刻。

那在有保质期的大前提下,爱又能是什么呢?

今泉力哉诠释到“爱是「一瞬の夢」”,灵魂交融的某一瞬间,那如梦似幻瞬间所对应的时刻的精准感受,便是“爱”。

那一瞬,是看大马戏时身旁的你,欢喜的掌声和笑容。

那一瞬,是坐火箭过山车时,你尖叫中紧握住我的手。

那一瞬,是凌晨四点告白时,黄埔大道那不灭的灯火。

那一瞬,是雨夜珠江边上,一齐撑伞漫步的诗情画意。

那一瞬,是很多破碎和心痛的开端。

没错,世界上一切的一切都是有保质期的,而「那一瞬」于我而言,即是永恒。

那如梦似幻的一瞬,于我而言,便是爱情。

爱情是一个个发生了积极共鸣的微小瞬间,是发生在彼时彼刻某时刻真实的、积极的、愉悦的体验。也就是说,「“永远”大概只是表达一种程度,“我永远爱你”的意思是,我在那一瞬间觉得我永远爱你了。」爱便是那一瞬间的感觉。

但需要区别是,这种爱情并不是一瞬间混沌时的冲动,而是我在那一瞬间感觉到的永恒。于是,会去构想未来种种细节,构筑属于我们的爱情大厦。即便未来某一瞬间因为种种原因导致它轰然倒塌了,在难过与痛苦之后,我也不会忘记构筑它时的欣喜和期盼。

这便是我眼中的爱情,不用纠结过去、永恒与未来,很多事情没有结局,就已经是我们当时力所能及最好的结局。

为什么我们需要爱情?

第二个问题,尝试解答一下为什么我们需要爱情?严格来说,这是从我的角度来解读的,可能并不能代表普罗大众的爱情诉求。那么问题可能要改成:为什么我需要爱情?

上一段恋情还是 17 年了,也是匆匆了断,我甚至会想自己是否或许不适合恋爱。这五年来一直在不断追寻,那么为什么我们要追寻爱情?

于我而言,因为害怕孤独。《岛上书店》有段话我特别喜欢:

因为从心底害怕自己不值得被爱,我们独来独往。然而就是因为独来独往,才让我们以为自己不值得被爱。有一天,你不知道是什么时候,你会驱车上路。有一天,你不知道是什么时候,你会遇到她。你会被爱,因为你今生第一次真正不再孤单。你会选择不再孤单下去。

被爱之后,我发现被重视的感觉是多么美好,原来这世界上还可以有一位那么美好的人会重视孤独的我。于是,我一直在寻找那个人。

快餐时代的恋情讲究广泛捕捞、迅速淘汰,有句话是这么说的:「如果不将就的话,你会一点点变老。生活会变得更艰难,你会更孤单。」

但我不会将就,我期待爱情到来的那一瞬间。但或许吧,不将就或许一辈子也不会遇见什么爱情,不会拥有「一瞬の夢」;也或许永远都不会有人爱我;但也或许,过得怎样,与此无关。

我寻求的期盼,是真实的体验,不是「一瞬の夢」,而是「一瞬の真实」。

被人所珍视的感觉,会让我摆脱孤单情愫,会让我的的确确、切切实实的感觉到自己活着。是真实,而不是梦。我不愿回忆起过往,如同虚幻的梦境。我每天都会写日记;和你在一起时额外的还会每天写手账;日程会用「ATracker」记录真实的时间经历到日历中,精确到秒;行走的路径会用「足迹」静默采集我的 GPS 信息。

为什么我在大数据时代还冒着风险时刻记录自己的行为日志?因为这会让我感觉到,我是真实地在这个世界上存在着。

除此之外还有一个很重要的原因,我是一个极其注重体验的人,在之前的博客中我曾经写到“生命的意义在于体验”,在于此时此刻最真实的人生体验。

《岛上书店》有这么一段话:

“我们不是我们所收集的、得到的、读到的东西,只要我们还活着,我们就是爱,我们所爱的事物,我们所爱的人。所有这些,我认为真的会存活下去。”

生命的意义并不是由所谓的“人生大事”来赋予的,而是落实到生活中无常的琐碎。这就是我的生命,我的意义。所以我想记录每一个瞬间,无论是快乐的也好、悲伤的也罢,每年年度总结中我都会晒出那一年的心情年历。因为,那就是我那一年最真实的瞬间。每每回忆起那些瞬间时,我都能真切地感受到彼时彼刻的场景与我的情绪,由此,我才能真切地感受到,我是真实地存在着的。

唯一确信的存在是当下,唯一重要的是现在、是此时此刻的感受。人首先必须存在,才谈得上有关人生的一切。我重视收集回忆的点滴,是因为我重视自己的存在与人生。

就此话题再回溯一下主题,人与人的灵魂,确实是很难相通的,不是通过简单的特征就能互相打符号标签的。就譬如我是 INFJ,无论怎么测都是 INFJ;你是 ENFP,闭着眼都能看出你是 ENFP。理应是天造地设的一对,但也仅仅只是理论标签的。

如果剥去我们身上的各种标签,还能依然相爱,且越来越爱,那爱的才是最真实的对方。

爱情的纽带是什么?

第三个问题,维系亲密关系的纽带是什么?

这个问题我曾思考过,并写下了七个点发给你:

  1. 情谊:彼此之间互有情愫,这是一切的基础
  2. 真诚:真诚的态度,保持坦诚沟通,不要封闭自己的心
  3. 理解:在坦诚的基础上,相互理解,能够体谅对方
  4. 信任:在理解的基础上,彼此相互信任
  5. 陪伴:不离不弃,陪伴是最长情的告白
  6. 分享欲:有充足的分享欲,自己亲身体验的一切,都想和对方分享
  7. 同频:有共同的兴趣爱好,有相近的成长方向,有一起努力的目标
  8. 积极:乐观开朗,做对方彼此的太阳

我们两人每一个点做的都很好,但还是分开了,可能我这份答卷根本就不对吧。

确实,亲密关系主要是一个不断磨合、不断升级的、自我披露的互惠过程。在这个过程中,每个人都感觉到自己内心深处被对方验证、理解和关心。当双方都将伴侣最终由此将对方纳入自我,会形成一种相互依存结构,意味着个体对伴侣的爱会成为彼此的动力、彼此生活中最重要的一部分。

我一旦陷入便会不计后果地全力去爱,但今晚我的朋友和我说这段时间的我已然不像是我了。他说我人生的优先级全都变了,把爱情放于首位其余的全都顺延置后了,而投入的太多,其余的只剩下太少。周末和日常下班鲜有时间去学习,日常鲜有时间陪伴朋友一齐玩耍,甚至于冷漠了一些回应和求助。

我于此无可奈何,在我心中这个顺位是一定没有问题的。我知道什么是我想要的,知道什么于我而言是最重要的。我时常内省,时常想保持最优秀的自己,不断前进前进。而我遇到爱情时,迷失了前进的方向。

我不知道这是对是错,可能根本不存在对错。但我也不知道这样的自己会错过什么、会留下什么遗憾,也不知道该用什么方式去实现梦想,我不知道命运会把我带向何方。只能跟着感觉,一步步前行,全由你的爱意提供原动力。

失去爱情之后,要如何自处?

最后一个问题,失去爱情之后,要如何自处?这其实也是自己现在,最想探讨的问题。

之前找房时有过感慨,找了这么多中介,最终只有最后一个中介能拿到中介费,哪怕之前的中介再辛苦,给我用心找了再好的房子,带我看了再多的房子,那也极可能是无用功,因为只有最后那个中介能拿到钱。

对比来看,每段恋情都是下一段的养料,最终只为了那位最正确的对象服务的。因为最终的真爱,能步入婚姻殿堂走向 HE 的,也就只有一位。

那么之前的一切,就都是没有意义了吗?

不是的。

最开始找房的时候,我其实没有标准,一位一位中介不断带我看了房子,我才知道自己喜欢的原来是客厅通阳台、小区有班车、大客厅、阳光卧室的需求。这里面每一个点,都是一次次不断总结出来的,最终才找到了现在的可能是最合适的房子。

而恋情如果做个不太合适的类比,每一段都给我带来了成长与遗憾。正如我去年玩《寻找天堂》所体会到的那样:

关于孤独,Faye 离别时说无论如何,孤独的时刻总是会来。可这就是成长的一部分,也是人生的一部分。它让我们与我们所爱的人相处的每个瞬间都无比珍贵,让我们的回忆价值千金。

关于选择,Sopia 说人的一生如此短暂,我们不可能完成每一件想做的事。无论我们做了什么,总有其他东西值得一试,总有另一条路值得一走。所以到最后,我们能做的,也是必须做的,是满足于自己所选择的路。

关于遗憾,Colin 临终前说:“那些我错过的机会,那些小意外,和我所有未竟的愿望…是的,它们还是我的遗憾。可它们让我拥有了现在的一切。而我所拥有的一切…无论什么都换不走。”

每个人都身陷孤独,但却恣意妄为。我想,人呢,只要看穿了这个世界的矫饰,世界便会属于你。 珍惜当下,珍惜此时此刻,珍惜身边的一切。

我们会和一个人恋爱,其实只是正好满足了自己某些需要的特质。这种人在世上还会有很多,所以不存在什么命中注定的唯一的爱情。但恰恰就是在那个时刻,我就是遇到了你,而不是什么别的人,这就是缘分。

但我可能不能和你走到最后,我总是说着全力去爱不要留下遗憾就好。此刻才发现,大概无论怎么选择都还是会遗憾。

这就是遗憾,是 Colin 临终的遗憾,是 Sopia 没有选择的路,但同时也是 Faye 最最最最珍视的瞬间,也是我最最最最最珍重的体验。

我们每个人都是独特的个体,每一段记忆都无法被复制,也不可能会重来。我能做的,只剩下去珍视它,把它好好地珍藏在自己的秘密匣子里。当未来某一天,偶然间拿起来它时,我能

回忆那看大马戏时的一瞬;

回忆起坐火箭过山车时的一瞬;

回忆起凌晨四点告白时的一瞬;

回忆起是雨夜珠江边上的一瞬;

回忆起那「一瞬の夢」时,还能体验到那真切、由衷的喜悦之情,便是我的人生意义。

《追忆潸然》

如果让我再选一次,还是会追上你,哪怕最后的结局只是体面分手。

回到主题,为什么这部剧、这篇文章叫《花束般的恋爱》?

我以前很讨厌别人送花,毕业典礼时明确要求好朋友不要送花,哪怕要送也送干花。因为我受不了花朵枯萎的过程,我知道它现在很美丽,但是我忍受不了它枯萎时的模样。

但是这个刻板印象最终在被我自己打破了,在为你准备鲜花时我才发现——原来鲜花那么美丽、送给那么美好的你是多么美好!

这或许就是鲜花的价值——即使爱情的花束会凋零,但它还是用力地、无比绚烂地绽放过。尤其,是当这束鲜花是精心的礼物。

而恋爱最珍贵的纪念物,是你留在我身上的、如同河川留给地形的,你对我造成的改变。

那些读过的书本、看过的电影、听过的音乐、吃过的美食、逛过的公园、看过的演出、爱过的人,它们早已融入了我的血与肉,塑造了我的现在,成为了我身体的一部分。这 20.5 天的花期,已然融入我的血肉中、我的习惯中,成为我未来生活的一部分。

花束般的恋爱

深爱的你消失于分手的那一刻,心中的爱与思念都只是属于自己曾经拥有过的纪念。我知道有些事情是不能遗忘的,有些事情是值得纪念的,有些事情是能够心甘情愿的,而有些事情是无可奈何的。

我总是不珍惜眼前人,在未可预知的未来里,我以为我们总会保持联系、总会一定再见、总会有机会说一句对不起、总以为过了今天一切都会好起来,但是我从没想过每一次挥手道别都可能是诀别。

所以最后的最后,请各位珍惜眼前的一切、珍视自己最真实的体验、珍重那「一瞬の夢」。

谢谢你曾给予我的爱情——花期 20.5 天的美好花束。

周刊(第2期):重拾失去的好奇心

重拾失去的好奇心

前两天读到了两篇关于好奇心的文章——《长大后,如果是时间修剪了我们的好奇心,我们应该责怪时间吗?》《重新理解「好奇心」:学习动机的自救指南》,那本期周刊就结合这两篇文章来简单谈一谈「好奇心」。

首先,先来介绍下这两篇文章的核心内容:

关于好奇心的形成,文章中谈到「好奇心,就是驱使人类去获取知识的情绪动力」,它「被激发时,会同步刺激尾状核,然后分泌让我们快乐的多巴胺,进而让我们产生愉悦感」。

好奇心是我们通往知识,总结规律的原初动力。但让人遗憾的是,随着年龄的增长,我们的好奇心会越变越少,再提不起读书的兴趣。它就像一只精灵,让人捉摸不透,在我们成长的不经意间,就消失不见。

关于好奇心的失去,文中认为有两点原因:

  • 内部原因:被岁月修剪
  • 外部原因:被技术截除

所谓「被岁月修剪」,文章写道「我们接收到信息一定是有偏颇和误差的,人们更愿意接受和自己想法、立场一致的信息(认知偏差),因而逐渐封闭了自己的认知」。其实就是融入社会之后,有条件地摄取那些符合我们认知模型的信息,由此而形成的信息茧房,让我们不断不断强化信念,从而逐渐失去好奇。「因为社会规训,我们的好奇心被不断修剪;又因为忙碌,我们的好奇心被逐步耗竭。」

所谓「被技术截除」,文中举了个例子:

互联网的搜索功能和算法功能,可以让你轻松找到任何想获得的信息,你可能刚输入了第一个字,搜索栏下就弹出了你最可能搜索的问题列表,甚至你还没有开始输入,搜索平台就已经呈现出了你想要的内容。就像哆啦 A 梦的任意门,你一起心动念,下一秒就到了你心之向往处。殊不知,去往目的地的沿途的景色,或者中途临时起意地一次探索,更让人惬意和弥足珍贵。

互联网的高效和瞬时,截除了许多我们对世界的接触的机会,从而截除了我们产生好奇的可能性。

以上两篇文章还谈及了好奇心的分类与病态型好奇,本期不再扩展去介绍,有兴趣的读者可以直接阅读原文。我这里想谈谈「好奇心的培养」。

之前看《列奥纳多·达·芬奇传》的时候才发觉到达·芬奇真的很厉害,原本只知他是个画家,但除此之外他在音乐、建筑、数学、几何学、解剖学、生理学、动物学、植物学、天文学、气象学、地质学、地理学、物理学、光学、力学、发明、土木工程等领域都有显著的成就:

  • 天文学方面,他猜想了「太阳不动」的日心说,这比哥白尼早 40 年;
  • 物理学方面,他发明了液压的联动器装置,比牛顿早200年提出重力的概念;
  • 光学方面,他第一个提出光和声音一样是一种波,而且光速是有限的;
  • 建筑学方面,他是桥梁、教堂、城堡、下水道的设计师,同时他最早提出了人车分流的设计理念;
  • 气象学方面,发现折射现象并解释天空为什么是蓝色的;
  • 军事方面,他发明过直升机、飞机、降落伞、机关炮、手榴弹、潜水艇、坦克、起重机和潜水装备。虽然未必都造了出来,但是那些图他早早地就都画好,他做的变速箱就是现在所用的汽车变速箱的原理。
  • ……等等

读到这些的时候,我甚至会产生他是不是穿越过来的人,为什么他一个人能发现、发明这么多东西。书中给出了解释,他的笔记手稿近 3 万余页,从现存的手稿中来看,达·芬奇是一个特别好奇的人,他会抓住一切机会向各式各样的专业人才请教他们专业的问题,每天生活在提问和探寻之中。直到临终前在日记中留下了这样的绝笔:「明天一天要搞清楚啄木鸟的舌头究竟是什么形状」。

我们现在人有人想过类似的问题吗?或许有,但只要一去搜索引擎去搜索立马就会出来答案。这是一个方便的年代,也是磨灭我们好奇心的年代。达·芬奇始终对这世界一切都饱含着兴趣,但他享受的不仅仅是问题的答案,而是探索答案本身的过程。他曾经画满了169页各种画圆为方的尝试、为探索一个问题几年不曾收手。他是非常愉快地、投入地处在心流状态当中度过他完美的一生。

上一个给我留下如此深刻印象的人物还是维特根斯坦。哲学的起源亦是对世界的好奇,求知是所有人的本性,人是由于好奇而开始哲学思考的。

不论现在还是最初,人都是由于好奇而开始哲学思考,开始是对身边所不懂的东西感到奇怪,继而逐步前进,而对更重大的事情发生疑问,例如关于月象的变化,关于太阳和星辰的变化,以及万物的生成。——亚里士多德

世界上的大部分知识对我们而言可能不需要知道,因为对生活没有任何实际用处。而这一切只是因为好奇,纯粹的好奇。

那么于我们而言,如何保持好奇心呢?我小结了三点:

  1. 勤于观察,穷追到底
  2. 延伸自己与世界的接触
  3. 拓展思考

勤于观察,穷追到底。这要求我们要时刻怀有孩童般的惊奇,保持敏锐的心勤于观察,不断地追问,「Stay hungry, stay foolish」,直到满足自己的求知欲。「学问」为什么叫「学问」?就是因为,问,才是更重要的。那么我们要追问什么呢?

延伸自己与世界的接触。对一切新事物不断去思考它是什么?它会带来什么?它意味着什么?它能够跟我产生什么样的联系? 绝大多数的机会,都是从各种各样的可能性中,孕育、碰撞而产生的,尽量去碰撞出世界之物与我之间的关联,从而产生「触点」。那么得到答案之后,我们要做什么呢?

拓展思考。就是要发现更大的空间和更多的可能性,针对得出答案不断地提出新问题。并且针对这些问题,去寻求信息,更新自己的认知,获取更多的「触点」,将它们连接在一 起,不断拓展自己的认知网络。而不是满足于它所呈现给你的风景,就此止步不前。之前说技术会截除好奇,搜索的便捷性会让我们止步于某处。但其实只要保持拓展思考的意识,主动追寻、善用技术,会发现技术不仅没有挤压好奇心,反而还给好奇心插上翅膀,让我们在信息的海洋和森林里自如穿梭,迸发出更强大的创造力和生命力。

综上,一切好奇心,皆始于观察。发现惊奇之物,要不断追问它的性质与它与我的联系,从而去碰撞可能性。最后拓展思考,去发现更大的空间和更多的可能性,就像旋转的漩涡一样,越来越大,从而不断循环孕育出更多的好奇。

每周推荐

软件:AltTab(macOS)

Mac 在不同窗口间切换比较麻烦,Cmd+ Tab 没有预览页面,且只能切换应用,无法切换应用内的各个窗口。

AltTab 可以完美解决 macOS 的这个痛点,使用 HomeBrew 即可安装:

brew install alt-tab

之后按住 Alt + Tab 即可切换系统下的各个窗口(同一应用也可),且提供了实时的预览图,方便用户准确切换到自己预期的窗口。

软件:Context(macOS)

Contexts 和 AltTab 属于竞品关系,老牌的「上下文」切换应用。

Flutter 3.0 发布

周三 Google I/O 上发布了 Flutter 3.0,主要特性为多平台友好, 本版本开始支持构建 macOS 与 Linux App。值得深思 Electron 将何去何从?

参考阅读:What’s new in Flutter 3 | Medium

React 状态管理类库

最后,推荐一些当下流行的 React 状态管理类库:

周刊(第1期):开刊,为什么写周刊

为什么写周刊

博客中都是些比较「重」的文章,每篇大概 4000 字起步,同时为了保障自己博客文字的质量,写作起来略感负担,需要耗费比较多的精力和时间。

记得前两年给自己设定了每月写一篇博客的 Flag,但是只坚持了 6 个月便断更了,因为自己工作忙起来之后,实在是抽不出连续的、大段的时间去沉静下来写文章。回顾起来,我能够稳定能输出文章的时间,都是五一长假、国庆长假。如此便导致文字输出的频率太低、同时主题单调,违背了写博客输出的初衷。

2021 年度总结 一文中,曾说过我这儿知识的「输入—输出」链路——平时阅读时我用「Drafts」快速地做摘录,这是「碎片摄取」;之后以每周为周期再进行「定期整合」,梳理到「LogSeq」中。

如下图,是简单整理之后的笔记:

但我感觉这样还仅仅只是「输入」,并没有发散自己的想法做到好的「输出」,也总有朋友吐槽我的产出太少。写博客是我喜欢的「输出」方式,但是我想要另一种更轻量、更频繁的方法输出自己的文字来表达自我,就像我 2014-2017 年那时候的老博客一样,满是随笔、无需雕琢。

上周我在博客中偶然发现,自己有个读者的网站里有一个 周刊专栏 ,还发现有一些同学的 周刊 同时也兼顾了一定的深度和思考,我非常喜欢定期输出与分享的这种形式。

让读书产生好处的最简单办法是,一旦有灵感和想法之后,马上写出来,公开发布在社交媒体上,即使不成熟也没关系。写的过程也是自己深度思考的一个步骤,外人的有价值评论可帮你不断推敲,或给你带来新的线索,积累多了自然会出深刻的洞见。一个人孤立封闭的傻读写笔记,很难迅速提高思考深度。—— 硅谷王川的微博

思考到最后,我还是决定写周刊。虽然很可能偶尔会鸽那么个一两期,但既然开刊了,就以这种定期输出的方式来鞭策自己前行。它于我的意义,便是反向鞭策自己高效输入,同时拉近读者之间的距离。我发现自己博客中兼顾了生活文和技术文,但读者更喜欢看我的生活文,尤其是每年的年度总结阅读量最高。哈哈,总有同学每年年尾都在催更年度总结。

好了,现在开刊的动机有了,那么周刊中会分享些什么呢?

如果单单只是笔记的分享和推荐的堆砌,没有质量起不到输出的效果。因此,每期周刊会分为两个部分:主题思考与推荐分享。

  • 主题思考:会定制一个主题,它是周刊的主体内容,我会分享关于这个主题的现象和思考,如同随笔一般,比如本期的主题其实就是「为什么写周刊」。
  • 推荐分享:大致会分为技术分享和生活分享,技术分享我看到的好工具、高质量的技术文章、工作中遇到的技术难点和细节;生活分享会分享自己的经历、阅读的内容和思考。

那么接下来就是“安利”环节啦~

每周推荐

关于过期

上周看完了《悖论 13》,世界末日的场景下主角团遇到了食物过期短缺的问题,由此想到了《重庆森林》里的一段话:

不知道从什么时候开始,在每个东西上面都有一个日子,秋刀鱼会过期,肉酱会过期,连保鲜纸都会过期。 我开始怀疑,在这个世界上,还有什么东西是不会过期的?

在这个世界上,还有什么东西是不会过期的呢?没有永恒的存在。

关于任天堂的游戏

上周看到了一个关于任天堂的视频 ,大家只要说到任天堂的游戏,就会立刻想到一个字——「好玩」。而正式横井军平奠基了任天堂的游戏风格:

“回避游戏创意不足的一条捷径就是参与CPU、画质、音质的竞争,用高性能来弥补游戏本身的不足”——横井军平

说到底,游戏性才是一个游戏最纯粹地去吸引玩家的东西。而扩大人口战略,也注定了任天堂的游戏都是老少咸宜、甚至是低幼的。关于这一点任天堂美国总裁Reggie Fils-Aime在接受《多伦多星报》采访时表示:

“我们非常高兴我们的竞争对手不关注孩子们和家庭游戏市场,这其实是一个非常重要的市场,因为现在5、6岁的孩子将来他们会长大,当他们十几二十岁时依然会是我们的忠实用户。而当你与你的家人一起玩《星之卡比》《塞尔达传说》或是《超级马里奥赛车》时,更能增进家人间的感情。”

而国内的环境则很难诞生出这样伟大的游戏公司——限发游戏版号,一纸通告就能废掉一个行业,弄人心惶惶,刑不可知,则威不可测。于是大家都想着捞快钱,变着花来玩各种氪金玩法,真的很难再诞生出一款优质的国产游戏了。

推荐观看:

引擎剖析:JS 中的字符串转数值

JS 中,字符串转数值的方式有以下 9 种:

  1. parseInt()
  2. parseFloat()
  3. Number()
  4. Double tilde (~~) Operator
  5. Unary Operator (+)
  6. Math.floor()
  7. Multiply with number
  8. The Signed Right Shift Operator(>>)
  9. The Unsigned Right Shift Operator(>>>)

这几种方式对运行结果的差异,如下表所示:

字符串转数值方案对比

对比表格的源码发布到了 https://airing.ursb.me/web/int.html,需要可自取。

除了运行结果上的存在差异之外,这些方法在性能上也存在着差异。在 NodeJS V8 环境下,这几个方法微基准测试的结果如下:

parseInt() x 19,140,190 ops/sec ±0.45% (92 runs sampled)
parseFloat() x 28,203,053 ops/sec ±0.25% (95 runs sampled)
Number() x 1,041,209,524 ops/sec ±0.20% (90 runs sampled)
Double tilde (~~) Operator x 1,035,220,963 ops/sec ±1.65% (97 runs sampled)
Math.floor() x 28,224,678 ops/sec ±0.23% (96 runs sampled)
Unary Operator (+) x 1,045,129,381 ops/sec ±0.17% (95 runs sampled)
Multiply with number x 1,044,176,084 ops/sec ±0.15% (93 runs sampled)
The Signed Right Shift Operator(>>) x 1,046,016,782 ops/sec ±0.11% (96 runs sampled)
The Unsigned Right Shift Operator(>>>) x 1,045,384,959 ops/sec ±0.08% (96 runs sampled)

可见,parseInt()parseFloat()Math.floor() 的效率最低,只有其他运算 2% 左右的效率,而其中又以parseInt()最慢,仅有 1%。

为什么这些方法存在着这些差异?这些运算在引擎层又是如何被解释执行的?接下来将从 V8、JavaScriptCore、QuickJS 等主流 JS 引擎的视角,探究这些方法的具体实现。

首先来看看 parsrInt()

1. parseInt()

ECMAScript (ECMA-262) parseInt

1.1 V8 中的 parseInt()

在 V8 [→ src/init/bootstrapper.cc] 中定义了 JS 语言内置的标准对象,我们可以找到其中关于 parseInt 的定义:

Handle<JSFunction> number_fun = InstallFunction(isolate_, global, "Number", JS_PRIMITIVE_WRAPPER_TYPE, JSPrimitiveWrapper::kHeaderSize, 0, isolate_->initial_object_prototype(), Builtin::kNumberConstructor);
// Install Number.parseInt and Global.parseInt.
Handle<JSFunction> parse_int_fun = SimpleInstallFunction(isolate_, number_fun, "parseInt", Builtin::kNumberParseInt, 2, true);
JSObject::AddProperty(isolate_, global_object, "parseInt", parse_int_fun,
native_context()->set_global_parse_int_fun(*parse_int_fun);

可以见,Number.parseInt 和全局对象的 parseInt 都是基于 SimpleInstallFunction 注册的,它会将 API 安装到 isolate 中,并将该方法与 Builtin 做绑定。JS 侧调用 pasreInt 即为引擎侧调用 Builtin::kNumberParseInt

Builtin (Built-in Functions) 是 V8 中在 VM 运行时可执行的代码块,用于表达运行时对 VM 的更改。目前 V8 版本中 Builtin 有下述 5 种实现方式:

  • Platform-dependent assembly language:很高效,但需要手动适配到所有平台,并且难以维护。
  • C++:风格与runtime functions非常相似,可以访问 V8 强大的运行时功能,但通常不适合性能敏感区域。
  • JavaScript:缓慢的运行时调用,受类型污染导致的不可预测的性能影响,以及复杂的 JS语义问题。现在 V8 不再使用 JavaScript 内置函数。
  • CodeStubAssembler:提供高效的低级功能,非常接近汇编语言,同时保持平台依赖无关性和可读性。
  • Torque:是 CodeStubAssembler 的改进版,其语法结合了 TypeScript 的一些特征,非常简单易读。强调在不损失性能的前提下尽量降低使用难度,让 Builtin 的开发更加容易一些。目前不少内置函数都是由 Torque 实现的。

回到前文 Builtin::kNumberParseInt 这个函数,在 [→ src/builtins/builtins.h] 中可以看到其定义:

// Convenience macro to avoid generating named accessors for all builtins.
#define BUILTIN_CODE(isolate, name) \
(isolate)->builtins()->code_handle(i::Builtin::k##name)

因此这个函数注册的原名是 NumberParseInt,实现在 [→ src/builtins/number.tq] 中,是个基于 Torque 的 Builtin 实现。

// ES6 #sec-number.parseint
transitioning javascript builtin NumberParseInt(
js-implicit context: NativeContext)(value: JSAny, radix: JSAny): Number {
return ParseInt(value, radix);
}
transitioning builtin ParseInt(implicit context: Context)(
input: JSAny, radix: JSAny): Number {
try {
// Check if radix should be 10 (i.e. undefined, 0 or 10).
if (radix != Undefined && !TaggedEqual(radix, SmiConstant(10)) &&
!TaggedEqual(radix, SmiConstant(0))) {
goto CallRuntime;
}
typeswitch (input) {
case (s: Smi): {
return s;
}
case (h: HeapNumber): {
// Check if the input value is in Signed32 range.
const asFloat64: float64 = Convert<float64>(h);
const asInt32: int32 = Signed(TruncateFloat64ToWord32(asFloat64));
// The sense of comparison is important for the NaN case.
if (asFloat64 == ChangeInt32ToFloat64(asInt32)) goto Int32(asInt32);
// Check if the absolute value of input is in the [1,1<<31[ range. Call
// the runtime for the range [0,1[ because the result could be -0.
const kMaxAbsValue: float64 = 2147483648.0;
const absInput: float64 = math::Float64Abs(asFloat64);
if (absInput < kMaxAbsValue && absInput >= 1.0) goto Int32(asInt32);
goto CallRuntime;
}
case (s: String): {
goto String(s);
}
case (HeapObject): {
goto CallRuntime;
}
}
} label Int32(i: int32) {
return ChangeInt32ToTagged(i);
} label String(s: String) {
// Check if the string is a cached array index.
const hash: NameHash = s.raw_hash_field;
if (IsIntegerIndex(hash) &&
hash.array_index_length < kMaxCachedArrayIndexLength) {
const arrayIndex: uint32 = hash.array_index_value;
return SmiFromUint32(arrayIndex);
}
// Fall back to the runtime.
goto CallRuntime;
} label CallRuntime {
tail runtime::StringParseInt(input, radix);
}
}

看这段代码前,先科普下 V8 中的几个数据结构:(V8 所有数据结构的定义可以见 [→ src/objects/objects.h])

  • Smi:继承自 Object,immediate small integer,只有 31 位
  • HeapObject:继承自 Object,superclass for everything allocated in the heap
  • PrimitiveHeapObject:继承自 HeapObject
  • HeapNumber:继承自 PrimitiveHeapObject,存储了数字的堆对象,用于保存大整形的对象。

我们知道 parseInt 接收两个形参, 即 parseInt(string, radix),此处亦如是。 实现流程如下:

  • 首先判断 radix 是否没传或者传了 0 或 10,如果不是,那么则不是十进制的转换,就走 runtime 中提供的 StringParseInt 函数 runtime::StringParseInt
  • 如果是十进制转换就继续走,判断第一个参数的数据类型。
  • 如果是 Smi 或者是没有越界(超 31 位)的 HeapNumber,那么就直接 return 入参,相当于没有转化;否则同样走 runtime::StringParseInt。注意如果这里越界了就会走 ChangeInt32ToTagged,其为 CodeStubAssembler 实现的一个函数,会强转 Int32,如果当前执行环境不允许溢出 32 位,那么转换之后的数字就会不合预期。
  • 如果是 String,则判断是否是 hash,如果是的就找到对应整型 value 返回;否则依然走 runtime::StringParseInt

那么焦点来到了 runtime::StringParseInt。[→ src/runtime/runtime-numbers.cc]

// ES6 18.2.5 parseInt(string, radix) slow path
RUNTIME_FUNCTION(Runtime_StringParseInt) {
HandleScope handle_scope(isolate);
DCHECK_EQ(2, args.length());
Handle<Object> string = args.at(0);
Handle<Object> radix = args.at(1);
// Convert {string} to a String first, and flatten it.
Handle<String> subject;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, subject,
Object::ToString(isolate, string));
subject = String::Flatten(isolate, subject);
// Convert {radix} to Int32.
if (!radix->IsNumber()) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, radix,
Object::ToNumber(isolate, radix));
}
int radix32 = DoubleToInt32(radix->Number());
if (radix32 != 0 && (radix32 < 2 || radix32 > 36)) {
return ReadOnlyRoots(isolate).nan_value();
}
double result = StringToInt(isolate, subject, radix32);
return *isolate->factory()->NewNumber(result);
}

这段逻辑比较简单,就不再一行行解读了。值得注意的是,根据标准,如果 radix 不在 2~36 的范围内,会返回 NaN。

1.2 JavaScriptCore 中的 parseInt()

接着我们来看看 JavaScriptCore 中的 parseInt()

JavaScriptCore 中关于 JS 语言内置对象的注册都在 [→ runtime/JSGlobalObjectFuntions.cpp] 文件中:

JSC_DEFINE_HOST_FUNCTION(globalFuncParseInt, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
JSValue value = callFrame->argument(0);
JSValue radixValue = callFrame->argument(1);
// Optimized handling for numbers:
// If the argument is 0 or a number in range 10^-6 <= n < INT_MAX+1, then parseInt
// results in a truncation to integer. In the case of -0, this is converted to 0.
//
// This is also a truncation for values in the range INT_MAX+1 <= n < 10^21,
// however these values cannot be trivially truncated to int since 10^21 exceeds
// even the int64_t range. Negative numbers are a little trickier, the case for
// values in the range -10^21 < n <= -1 are similar to those for integer, but
// values in the range -1 < n <= -10^-6 need to truncate to -0, not 0.
static const double tenToTheMinus6 = 0.000001;
static const double intMaxPlusOne = 2147483648.0;
if (value.isNumber()) {
double n = value.asNumber();
if (((n < intMaxPlusOne && n >= tenToTheMinus6) || !n) && radixValue.isUndefinedOrNull())
return JSValue::encode(jsNumber(static_cast<int32_t>(n)));
}
// If ToString throws, we shouldn't call ToInt32.
return toStringView(globalObject, value, [&] (StringView view) {
return JSValue::encode(jsNumber(parseInt(view, radixValue.toInt32(globalObject))));
});
}

WebKit 中的代码注释都很详尽易读,这里也不再解读了。最后,会调用 parseInt,JavaScriptCore 的 parseInt 的实现全放在了 [→ runtime/ParseInt.h] 中,核心代码如下:

ALWAYS_INLINE static bool isStrWhiteSpace(UChar c)
{
// https://tc39.github.io/ecma262/#sec-tonumber-applied-to-the-string-type
return Lexer<UChar>::isWhiteSpace(c) || Lexer<UChar>::isLineTerminator(c);
}
// ES5.1 15.1.2.2
template <typename CharType>
ALWAYS_INLINE
static double parseInt(StringView s, const CharType* data, int radix)
{
// 1. Let inputString be ToString(string).
// 2. Let S be a newly created substring of inputString consisting of the first character that is not a
// StrWhiteSpaceChar and all characters following that character. (In other words, remove leading white
// space.) If inputString does not contain any such characters, let S be the empty string.
int length = s.length();
int p = 0;
while (p < length && isStrWhiteSpace(data[p]))
++p;
// 3. Let sign be 1.
// 4. If S is not empty and the first character of S is a minus sign -, let sign be -1.
// 5. If S is not empty and the first character of S is a plus sign + or a minus sign -, then remove the first character from S.
double sign = 1;
if (p < length) {
if (data[p] == '+')
++p;
else if (data[p] == '-') {
sign = -1;
++p;
}
}
// 6. Let R = ToInt32(radix).
// 7. Let stripPrefix be true.
// 8. If R != 0,then
// b. If R != 16, let stripPrefix be false.
// 9. Else, R == 0
// a. LetR = 10.
// 10. If stripPrefix is true, then
// a. If the length of S is at least 2 and the first two characters of S are either ―0x or ―0X,
// then remove the first two characters from S and let R = 16.
// 11. If S contains any character that is not a radix-R digit, then let Z be the substring of S
// consisting of all characters before the first such character; otherwise, let Z be S.
if ((radix == 0 || radix == 16) && length - p >= 2 && data[p] == '0' && (data[p + 1] == 'x' || data[p + 1] == 'X')) {
radix = 16;
p += 2;
} else if (radix == 0)
radix = 10;
// 8.a If R < 2 or R > 36, then return NaN.
if (radix < 2 || radix > 36)
return PNaN;
// 13. Let mathInt be the mathematical integer value that is represented by Z in radix-R notation, using the letters
// A-Z and a-z for digits with values 10 through 35. (However, if R is 10 and Z contains more than 20 significant
// digits, every significant digit after the 20th may be replaced by a 0 digit, at the option of the implementation;
// and if R is not 2, 4, 8, 10, 16, or 32, then mathInt may be an implementation-dependent approximation to the
// mathematical integer value that is represented by Z in radix-R notation.)
// 14. Let number be the Number value for mathInt.
int firstDigitPosition = p;
bool sawDigit = false;
double number = 0;
while (p < length) {
int digit = parseDigit(data[p], radix);
if (digit == -1)
break;
sawDigit = true;
number *= radix;
number += digit;
++p;
}
// 12. If Z is empty, return NaN.
if (!sawDigit)
return PNaN;
// Alternate code path for certain large numbers.
if (number >= mantissaOverflowLowerBound) {
if (radix == 10) {
size_t parsedLength;
number = parseDouble(s.substring(firstDigitPosition, p - firstDigitPosition), parsedLength);
} else if (radix == 2 || radix == 4 || radix == 8 || radix == 16 || radix == 32)
number = parseIntOverflow(s.substring(firstDigitPosition, p - firstDigitPosition), radix);
}
// 15. Return sign x number.
return sign * number;
}
ALWAYS_INLINE static double parseInt(StringView s, int radix)
{
if (s.is8Bit())
return parseInt(s, s.characters8(), radix);
return parseInt(s, s.characters16(), radix);
}
template<typename CallbackWhenNoException>
static ALWAYS_INLINE typename std::invoke_result<CallbackWhenNoException, StringView>::type toStringView(JSGlobalObject* globalObject, JSValue value, CallbackWhenNoException callback)
{
VM& vm = getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
JSString* string = value.toStringOrNull(globalObject);
EXCEPTION_ASSERT(!!scope.exception() == !string);
if (UNLIKELY(!string))
return { };
auto viewWithString = string->viewWithUnderlyingString(globalObject);
RETURN_IF_EXCEPTION(scope, { });
RELEASE_AND_RETURN(scope, callback(viewWithString.view));
}
// Mapping from integers 0..35 to digit identifying this value, for radix 2..36.
const char radixDigits[] = "0123456789abcdefghijklmnopqrstuvwxyz";

直接贴出了代码,因为 JavaScriptCore 中的 API 都是严格按照 ECMAScript (ECMA-262) parseInt 标准一步一步按流程实现,可读性和注释也很好,强烈建议读者自己阅读一下,此处不再解读。

1.3 QuickJS 中的 parseInt()

QuickJS 的核心代码都在 [→ quickjs.c] 中,首先是 parseInt 的注册代码:

/* global object */
static const JSCFunctionListEntry js_global_funcs[] = {
JS_CFUNC_DEF("parseInt", 2, js_parseInt ),
//...
}

js_parseInt 的实现逻辑如下:

static JSValue js_parseInt(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
const char *str, *p;
int radix, flags;
JSValue ret;
str = JS_ToCString(ctx, argv[0]);
if (!str)
return JS_EXCEPTION;
if (JS_ToInt32(ctx, &radix, argv[1])) {
JS_FreeCString(ctx, str);
return JS_EXCEPTION;
}
if (radix != 0 && (radix < 2 || radix > 36)) {
ret = JS_NAN;
} else {
p = str;
p += skip_spaces(p);
flags = ATOD_INT_ONLY | ATOD_ACCEPT_PREFIX_AFTER_SIGN;
ret = js_atof(ctx, p, NULL, radix, flags);
}
JS_FreeCString(ctx, str);
return ret;
}

Bellard 大神的代码注释很少,但同时也非常精炼。

至此,本文介绍完了三个引擎下各自 parseInt 的实现,三者都是基于标准的实现,但由于代码风格不同,读起来也像是阅读三个风格不同散文大家的作品。

不过标准和实现,我们可以发现 parseInt 在真正执行字符串转数字这个操作做了非常多的前置操作,如入参合法判断、入参默认值、字符串格式判断与规整化、越界判断等等,最后再交由 runtime 处理。因此,我们不难推出其效率略低的原因。

接下来,我们再简单看看 parseFloat

2. parseFloat()

ECMAScript (ECMA-262) parseFloat

根据标准,parseFloat 与 parseInt 有两点明显的不同:

  1. 仅支持一个入参,不支持进制转换
  2. 返回值支持浮点型

2.1 V8 中的 parseFloat()

V8 中 parseFloat 的相关逻辑都紧挨着 parseInt,这里直接贴出关键实现:

[→ src/builtins/number.tq]

// ES6 #sec-number.parsefloat
transitioning javascript builtin NumberParseFloat(
js-implicit context: NativeContext)(value: JSAny): Number {
try {
typeswitch (value) {
case (s: Smi): {
return s;
}
case (h: HeapNumber): {
// The input is already a Number. Take care of -0.
// The sense of comparison is important for the NaN case.
return (Convert<float64>(h) == 0) ? SmiConstant(0) : h;
}
case (s: String): {
goto String(s);
}
case (HeapObject): {
goto String(string::ToString(context, value));
}
}
} label String(s: String) {
// Check if the string is a cached array index.
const hash: NameHash = s.raw_hash_field;
if (IsIntegerIndex(hash) &&
hash.array_index_length < kMaxCachedArrayIndexLength) {
const arrayIndex: uint32 = hash.array_index_value;
return SmiFromUint32(arrayIndex);
}
// Fall back to the runtime to convert string to a number.
return runtime::StringParseFloat(s);
}
}

[→ src/runtime/runtime-numbers.cc]

// ES6 18.2.4 parseFloat(string)
RUNTIME_FUNCTION(Runtime_StringParseFloat) {
HandleScope shs(isolate);
DCHECK_EQ(1, args.length());
Handle<String> subject = args.at<String>(0);
double value = StringToDouble(isolate, subject, ALLOW_TRAILING_JUNK,
std::numeric_limits<double>::quiet_NaN());
return *isolate->factory()->NewNumber(value);
}

因标准中的流程更为简易,因此较 parseInt 而言, parseFloat 更加简单易读。

2.2 JavaScriptCore 中的 parseFloat()

在 JavaScriptCore 中,parseFloat 的逻辑则更加简洁明了:

static double parseFloat(StringView s)
{
unsigned size = s.length();
if (size == 1) {
UChar c = s[0];
if (isASCIIDigit(c))
return c - '0';
return PNaN;
}
if (s.is8Bit()) {
const LChar* data = s.characters8();
const LChar* end = data + size;
// Skip leading white space.
for (; data < end; ++data) {
if (!isStrWhiteSpace(*data))
break;
}
// Empty string.
if (data == end)
return PNaN;
return jsStrDecimalLiteral(data, end);
}
const UChar* data = s.characters16();
const UChar* end = data + size;
// Skip leading white space.
for (; data < end; ++data) {
if (!isStrWhiteSpace(*data))
break;
}
// Empty string.
if (data == end)
return PNaN;
return jsStrDecimalLiteral(data, end);
}

2.3 QuickJS 中的 parseFloat()

而对比 JavaScriptCore,QuickJS 则短短 12 行:

[→ quickjs.c]

static JSValue js_parseFloat(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
const char *str, *p;
JSValue ret;
str = JS_ToCString(ctx, argv[0]);
if (!str)
return JS_EXCEPTION;
p = str;
p += skip_spaces(p);
ret = js_atof(ctx, p, NULL, 10, 0);
JS_FreeCString(ctx, str);
return ret;
}

不过对比之后可以知道,QuickJS 这里之所以短小,是没有做 ASCII 和 8Bit 的兼容。阅读 ECMAScript (ECMA-262) parseFloat 之后可以发现,QuickJS 这里的处理其实没有什么问题,最新的标准中并没有要求解释器要这样的兼容。

3. Number()

ECMAScript (ECMA-262) Number ( value )

3.1 V8 中的 Number()

Number 作为全局对象,定义还是在 [→ src/init/bootstrapper.cc] 中,在前文介绍 Number.parseInt 的注册时已然介绍过,我们回顾下:

Handle<JSFunction> number_fun = InstallFunction(
isolate_, global, "Number", JS_PRIMITIVE_WRAPPER_TYPE,
JSPrimitiveWrapper::kHeaderSize, 0,
isolate_->initial_object_prototype(), Builtin::kNumberConstructor);
number_fun->shared().DontAdaptArguments();
number_fun->shared().set_length(1);
InstallWithIntrinsicDefaultProto(isolate_, number_fun,
Context::NUMBER_FUNCTION_INDEX);
// Create the %NumberPrototype%
Handle<JSPrimitiveWrapper> prototype = Handle<JSPrimitiveWrapper>::cast(
factory->NewJSObject(number_fun, AllocationType::kOld));
prototype->set_value(Smi::zero());
JSFunction::SetPrototype(number_fun, prototype);
// Install the "constructor" property on the {prototype}.
JSObject::AddProperty(isolate_, prototype, factory->constructor_string(),
number_fun, DONT_ENUM);

这段代码处理注册了 Number 这个对象之外,还初始化了它的原型链,并把构造函数添加到了它的原型链上。构造函数 Builtin::kNumberConstructor 是 Torque 实现的 Builtin,[→ src/builtins/constructor.tq] ,具体实现如下:

// ES #sec-number-constructor
transitioning javascript builtin
NumberConstructor(
js-implicit context: NativeContext, receiver: JSAny, newTarget: JSAny,
target: JSFunction)(...arguments): JSAny {
// 1. If no arguments were passed to this function invocation, let n be +0.
let n: Number = 0;
if (arguments.length > 0) {
// 2. Else,
// a. Let prim be ? ToNumeric(value).
// b. If Type(prim) is BigInt, let n be the Number value for prim.
// c. Otherwise, let n be prim.
const value = arguments[0];
n = ToNumber(value, BigIntHandling::kConvertToNumber);
}
// 3. If NewTarget is undefined, return n.
if (newTarget == Undefined) return n;
// 4. Let O be ? OrdinaryCreateFromConstructor(NewTarget,
// "%NumberPrototype%", « [[NumberData]] »).
// 5. Set O.[[NumberData]] to n.
// 6. Return O.
// We ignore the normal target parameter and load the value from the
// current frame here in order to reduce register pressure on the fast path.
const target: JSFunction = LoadTargetFromFrame();
const result = UnsafeCast<JSPrimitiveWrapper>(
FastNewObject(context, target, UnsafeCast<JSReceiver>(newTarget)));
result.value = n;
return result;
}

注释中的 1-6 一一对应着[ECMAScript (ECMA-262) Number ( value )]标准中的流程 1-6,因此本文不再花篇章赘述其实现。需要注意的是,标准中明确说明了 Number 是支持 BigInt 的,各引擎的实现也着重注意了这点,这也证明了我们之前运算对照表中的结果。

3.2 JavaScriptCore 中的 Number()

JavaScriptCore 中的这段代码则缺少注释,但逻辑上与 V8 一模一样,遵循标准:

[→ runtime/NumberConstructor.cpp]

// ECMA 15.7.1
JSC_DEFINE_HOST_FUNCTION(constructNumberConstructor, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
double n = 0;
if (callFrame->argumentCount()) {
JSValue numeric = callFrame->uncheckedArgument(0).toNumeric(globalObject);
RETURN_IF_EXCEPTION(scope, { });
if (numeric.isNumber())
n = numeric.asNumber();
else {
ASSERT(numeric.isBigInt());
numeric = JSBigInt::toNumber(numeric);
ASSERT(numeric.isNumber());
n = numeric.asNumber();
}
}
JSObject* newTarget = asObject(callFrame->newTarget());
Structure* structure = JSC_GET_DERIVED_STRUCTURE(vm, numberObjectStructure, newTarget, callFrame->jsCallee());
RETURN_IF_EXCEPTION(scope, { });
NumberObject* object = NumberObject::create(vm, structure);
object->setInternalValue(vm, jsNumber(n));
return JSValue::encode(object);
}

3.3 QuickJS 中的 Number()

Number 对象及其原型链的注册代码如下所示:

[→ quickjs.c]

void JS_AddIntrinsicBaseObjects(JSContext *ctx)
{
//...
/* Number */
ctx->class_proto[JS_CLASS_NUMBER] = JS_NewObjectProtoClass(ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_NUMBER);
JS_SetObjectData(ctx, ctx->class_proto[JS_CLASS_NUMBER], JS_NewInt32(ctx, 0));
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_NUMBER], js_number_proto_funcs, countof(js_number_proto_funcs));
number_obj = JS_NewGlobalCConstructor(ctx, "Number", js_number_constructor, 1, ctx->class_proto[JS_CLASS_NUMBER]);
JS_SetPropertyFunctionList(ctx, number_obj, js_number_funcs, countof(js_number_funcs));
}

同样的时候,在原型链注册的时候绑上了构造函数 js_number_constructor

static JSValue js_number_constructor(JSContext *ctx, JSValueConst new_target,
int argc, JSValueConst *argv)
{
JSValue val, obj;
if (argc == 0) {
val = JS_NewInt32(ctx, 0);
} else {
val = JS_ToNumeric(ctx, argv[0]);
if (JS_IsException(val))
return val;
switch(JS_VALUE_GET_TAG(val)) {
#ifdef CONFIG_BIGNUM
case JS_TAG_BIG_INT:
case JS_TAG_BIG_FLOAT:
{
JSBigFloat *p = JS_VALUE_GET_PTR(val);
double d;
bf_get_float64(&p->num, &d, BF_RNDN);
JS_FreeValue(ctx, val);
val = __JS_NewFloat64(ctx, d);
}
break;
case JS_TAG_BIG_DECIMAL:
val = JS_ToStringFree(ctx, val);
if (JS_IsException(val))
return val;
val = JS_ToNumberFree(ctx, val);
if (JS_IsException(val))
return val;
break;
#endif
default:
break;
}
}
if (!JS_IsUndefined(new_target)) {
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_NUMBER);
if (!JS_IsException(obj))
JS_SetObjectData(ctx, obj, val);
return obj;
} else {
return val;
}
}

值得关注的是 QuickJS 追求精简小巧,因此可以自行配置是否支持 BigInt,其余逻辑依然遵循标准。

4. Double tilde (~~) Operator

ECMAScript (ECMA-262) Bitwise NOT Operator

使用 ~ 运算符利用到了标准中的第 2 步,对被计算的值做类型转换,从而将字符串转成数值。这里我们关注这个环节具体是在引擎中的哪个步骤完成的。

4.1 V8 中的 BitwiseNot

首先看看 V8 中对一元运算符的判断:

[→ src/parsing/token.h]

static bool IsUnaryOp(Value op) { return base::IsInRange(op, ADD, VOID); }

定义在 ADD 和 VOID 范围内的 op,都是一元运算符,具体包括 (可见 [→ src/parsing/token.h]),其中 SUB 和 ADD 定义在二元运算符列表的末端,在 IsUnaryOp 中它们也会命中一元符的判断:

E(T, ADD, "+", 12)
E(T, SUB, "-", 12)
T(NOT, "!", 0)
T(BIT_NOT, "~", 0)
K(DELETE, "delete", 0)
K(TYPEOF, "typeof", 0)
K(VOID, "void", 0)

之后进入语法分析阶段,解析 AST 树的过程中,遇到一元运算符会做相应的处理,先调用 ParseUnaryOrPrefixExpression 之后构建一元运算符表达式 BuildUnaryExpression

[→ src/parsing/parser-base.h]

template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseUnaryExpression() {
// UnaryExpression ::
// PostfixExpression
// 'delete' UnaryExpression
// 'void' UnaryExpression
// 'typeof' UnaryExpression
// '++' UnaryExpression
// '--' UnaryExpression
// '+' UnaryExpression
// '-' UnaryExpression
// '~' UnaryExpression
// '!' UnaryExpression
// [+Await] AwaitExpression[?Yield]
Token::Value op = peek();
// 一元运算符处理
if (Token::IsUnaryOrCountOp(op)) return ParseUnaryOrPrefixExpression();
if (is_await_allowed() && op == Token::AWAIT) {
// await 处理
return ParseAwaitExpression();
}
return ParsePostfixExpression();
}
template <typename Impl>
typename ParserBase<Impl>::ExpressionT
ParserBase<Impl>::ParseUnaryOrPrefixExpression() {
//...
//...
// Allow the parser's implementation to rewrite the expression.
return impl()->BuildUnaryExpression(expression, op, pos);
}

[→ src/parsing/parser.cc]

Expression* Parser::BuildUnaryExpression(Expression* expression,
Token::Value op, int pos) {
DCHECK_NOT_NULL(expression);
const Literal* literal = expression->AsLiteral();
if (literal != nullptr) {
// !
if (op == Token::NOT) {
// Convert the literal to a boolean condition and negate it.
return factory()->NewBooleanLiteral(literal->ToBooleanIsFalse(), pos);
} else if (literal->IsNumberLiteral()) {
// Compute some expressions involving only number literals.
double value = literal->AsNumber();
switch (op) {
// +
case Token::ADD:
return expression;
// -
case Token::SUB:
return factory()->NewNumberLiteral(-value, pos);
// ~
case Token::BIT_NOT:
return factory()->NewNumberLiteral(~DoubleToInt32(value), pos);
default:
break;
}
}
}
return factory()->NewUnaryOperation(op, expression, pos);
}

如果字面量是数值型且一元运算符此刻不是 NOT(!),那么会把 Value 会转成 Number,如果是 BIT_NOT 再转成 INT32 进行取反运算。

4.2 JavaScriptCore 中的 BitwiseNot

同样在语法分析生成 AST 阶段,处理到 TILDE(~) 这个 token 后,创建表达式时会做类型转换的工作:

[→ Parser/Parser.cpp]

template <typename LexerType>
template <class TreeBuilder> TreeExpression Parser<LexerType>::parseUnaryExpression(TreeBuilder& context)
{
//... 省略无关代码
while (tokenStackDepth) {
switch (tokenType) {
//... 省略无关代码
// ~
case TILDE:
expr = context.makeBitwiseNotNode(location, expr);
break;
// +
case PLUS:
expr = context.createUnaryPlus(location, expr);
break;
//... 省略无关代码
}
}
}

[→ parser/ASTBuilder.h]

ExpressionNode* ASTBuilder::makeBitwiseNotNode(const JSTokenLocation& location, ExpressionNode* expr)
{
if (expr->isNumber())
return createIntegerLikeNumber(location, ~toInt32(static_cast<NumberNode*>(expr)->value()));
return new (m_parserArena) BitwiseNotNode(location, expr);
}

[→ parser/NodeConstructors.h]

inline BitwiseNotNode::BitwiseNotNode(const JSTokenLocation& location, ExpressionNode* expr)
: UnaryOpNode(location, ResultType::forBitOp(), expr, op_bitnot)
{
}

[→ parser/ResultType.h]

static constexpr ResultType forBitOp()
{
return bigIntOrInt32Type();
}
static constexpr ResultType bigIntOrInt32Type()
{
return ResultType(TypeMaybeBigInt | TypeInt32 | TypeMaybeNumber);
}

4.3 QuickJS 中的 BitwiseNot

QuickJS 在语法分析阶段,遇到 ~ 这个 token 会调用 emit_op(s, OP_not)

[→ quickjs.c]

/* allowed parse_flags: PF_ARROW_FUNC, PF_POW_ALLOWED, PF_POW_FORBIDDEN */
static __exception int js_parse_unary(JSParseState *s, int parse_flags)
{
int op;
switch(s->token.val) {
case '+':
case '-':
case '!':
case '~':
case TOK_VOID:
op = s->token.val;
if (next_token(s))
return -1;
if (js_parse_unary(s, PF_POW_FORBIDDEN))
return -1;
switch(op) {
case '-':
emit_op(s, OP_neg);
break;
case '+':
emit_op(s, OP_plus);
break;
case '!':
emit_op(s, OP_lnot);
break;
case '~':
emit_op(s, OP_not);
break;
case TOK_VOID:
emit_op(s, OP_drop);
emit_op(s, OP_undefined);
break;
default:
abort();
}
parse_flags = 0;
break;
//...
}
//...
}
}

emit_op 会生成 OP_not 字节码操作符,并将源码保存在 fd->byte_code 里。

static void emit_op(JSParseState *s, uint8_t val)
{
JSFunctionDef *fd = s->cur_func;
DynBuf *bc = &fd->byte_code;
/* Use the line number of the last token used, not the next token,
nor the current offset in the source file.
*/
if (unlikely(fd->last_opcode_line_num != s->last_line_num)) {
dbuf_putc(bc, OP_line_num);
dbuf_put_u32(bc, s->last_line_num);
fd->last_opcode_line_num = s->last_line_num;
}
fd->last_opcode_pos = bc->size;
dbuf_putc(bc, val);
}
int dbuf_putc(DynBuf *s, uint8_t c)
{
return dbuf_put(s, &c, 1);
}
int dbuf_put(DynBuf *s, const uint8_t *data, size_t len)
{
if (unlikely((s->size + len) > s->allocated_size)) {
if (dbuf_realloc(s, s->size + len))
return -1;
}
memcpy(s->buf + s->size, data, len);
s->size += len;
return 0;
}

QuickJS 解释执行的函数是 JS_EvalFunctionInternal,其会调用 JS_CallFree 进行字节码的解释执行,其核心逻辑是调用的 JS_CallInternal 函数。

/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */
static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
JSValueConst this_obj, JSValueConst new_target,
int argc, JSValue *argv, int flags)
{
JSRuntime *rt = caller_ctx->rt;
JSContext *ctx;
JSObject *p;
JSFunctionBytecode *b;
JSStackFrame sf_s, *sf = &sf_s;
const uint8_t *pc;
// ...省略无关代码
for(;;) {
int call_argc;
JSValue *call_argv;
SWITCH(pc) {
// ...
CASE(OP_not):
{
JSValue op1;
op1 = sp[-1];
// 如果是整型
if (JS_VALUE_GET_TAG(op1) == JS_TAG_INT) {
sp[-1] = JS_NewInt32(ctx, ~JS_VALUE_GET_INT(op1));
// 如果不是整型
} else {
if (js_not_slow(ctx, sp))
goto exception;
}
}
BREAK;
// ...
}
// ...
}

可见,解析到 OP_not 时, 如果是整型就直接取反,否则就调用 js_not_slow

static no_inline int js_not_slow(JSContext *ctx, JSValue *sp)
{
int32_t v1;
if (unlikely(JS_ToInt32Free(ctx, &v1, sp[-1]))) {
sp[-1] = JS_UNDEFINED;
return -1;
}
sp[-1] = JS_NewInt32(ctx, ~v1);
return 0;
}

js_not_slow 会尝试转整型,转不了就转 -1,转的了就转整型后取反。JS_ToInt32Free 转换逻辑如下:

/* return (<0, 0) in case of exception */
static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val)
{
redo:
tag = JS_VALUE_GET_NORM_TAG(val);
switch(tag) {
case JS_TAG_INT:
case JS_TAG_BOOL:
case JS_TAG_NULL:
case JS_TAG_UNDEFINED:
ret = JS_VALUE_GET_INT(val);
break;
// ...
default:
val = JS_ToNumberFree(ctx, val);
if (JS_IsException(val)) {
*pres = 0;
return -1;
}
goto redo;
}
*pres = ret;
return 0;
}

对于字符串,会走到 JS_ToNumberFree,之后调用 JS_ToNumberHintFree,涉及到字符串处理的核心逻辑如下:

static JSValue JS_ToNumberHintFree(JSContext *ctx, JSValue val,
JSToNumberHintEnum flag)
{
uint32_t tag;
JSValue ret;
redo:
tag = JS_VALUE_GET_NORM_TAG(val);
switch(tag) {
// ...省略无关逻辑
case JS_TAG_STRING:
{
const char *str;
const char *p;
size_t len;
str = JS_ToCStringLen(ctx, &len, val);
JS_FreeValue(ctx, val);
if (!str)
return JS_EXCEPTION;
p = str;
p += skip_spaces(p);
if ((p - str) == len) {
ret = JS_NewInt32(ctx, 0);
} else {
int flags = ATOD_ACCEPT_BIN_OCT;
ret = js_atof(ctx, p, &p, 0, flags);
if (!JS_IsException(ret)) {
p += skip_spaces(p);
if ((p - str) != len) {
JS_FreeValue(ctx, ret);
ret = JS_NAN;
}
}
}
JS_FreeCString(ctx, str);
}
break;
// ...省略无关逻辑
}
// ...省略无关逻辑
}

可以转化的用 JS_NewInt32 去处理,否则返回 NaN。

5. Unary Operator (+)

ECMAScript (ECMA-262) Unary Plus Operator

一元运算符加号是笔者最喜欢用的一种字符串转数值的方式,标准中它没有什么花里胡哨的、非常简介明了,就是用来做数值类型转换的。

5.1 V8 中的 UnaryPlus

语法分析阶段同 Double tilde (~~) Operator,此处不再赘述。

5.2 JavaScriptCore 中的 UnaryPlus

语法分析阶段同 Double tilde (~~) Operator,此处不再赘述。

5.3 QuickJS 中的 UnaryPlus

语法分析阶段同 Double tilde (~~) Operator,此处不再赘述。最后依然走到 JS_CallInternal

[→ quickjs.c]

/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */
static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
JSValueConst this_obj, JSValueConst new_target,
int argc, JSValue *argv, int flags)
{
JSRuntime *rt = caller_ctx->rt;
JSContext *ctx;
JSObject *p;
JSFunctionBytecode *b;
JSStackFrame sf_s, *sf = &sf_s;
const uint8_t *pc;
// ...省略无关代码
for(;;) {
int call_argc;
JSValue *call_argv;
SWITCH(pc) {
// ...
CASE(OP_plus):
{
JSValue op1;
uint32_t tag;
op1 = sp[-1];
tag = JS_VALUE_GET_TAG(op1);
if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64(tag)) {
} else {
if (js_unary_arith_slow(ctx, sp, opcode))
goto exception;
}
BREAK;
}
// ...省略无关代码
}
}
// ...省略无关代码
}

可以发现当操作数是 Int 或 Float 时,就直接不处理,和标准中规范的一致。而其他情况就调用 js_unary_arith_slow,若调用过程中遇到异常就走异常逻辑:

static no_inline __exception int js_unary_arith_slow(JSContext *ctx, JSValue *sp, OPCodeEnum op)
{
JSValue op1;
double d;
op1 = sp[-1];
if (unlikely(JS_ToFloat64Free(ctx, &d, op1))) {
sp[-1] = JS_UNDEFINED;
return -1;
}
switch(op) {
case OP_inc:
d++;
break;
case OP_dec:
d--;
break;
case OP_plus:
break;
case OP_neg:
d = -d;
break;
default:
abort();
}
sp[-1] = JS_NewFloat64(ctx, d);
return 0;
}

这里的 JS_ToFloat64Free 的内部处理逻辑和和 4.3 时的 JS_ToFloat64Free 一样,不再赘述。js_unary_arith_slow 处理完数值转换之后,若运算符是一元运算加号,则直接返回;否则还会根据运算符再做相应的运算处理,如自增符还需要+1 等。


至此,我们讲解了以下 5 个方法在解释器中的具体实现:

  1. parseInt()
  2. parseFloat()
  3. Number()
  4. Double tilde (~~) Operator
  5. Unary Operator (+)

除却以上 5 个数值转换方法之外,还有以下 4 个方法,因篇幅问题本文暂且不再详述:

  • Math.floor()
  • Multiply with number
  • The Signed Right Shift Operator(>>)
  • The Unsigned Right Shift Operator(>>>)

字符串转数值各有优劣,使用者可根据自己的需要进行选用,以下是我个人总结的一些经验:

如果返回值只要求整形:

  • 追求代码简洁和执行效率,对输入值有一定的把握(无需防御),优先选用 Unary Operator (+)
  • 对输入值没有把握,需要做防御式编程,使用 parseInt()
  • 需要支持 BigInt, 优先考虑使用 Number() ;如果用 Double tilde (~~) Operator,需要注意 31 位问题。

如果返回值要求浮点型:

  • 追求代码简洁和执行效率,对输入值有一定的把握(无需防御),优先选用 Unary Operator (+)
  • 对输入值没有把握,需要做防御式编程,使用 parseFloat()
  • 需要支持 BigInt,使用 parseFloat()
❌