普通视图

发现新文章,点击刷新页面。
昨天以前Airing 的博客

Airing 开通了一个咨询服务

2024年5月13日 19:12

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

方式 1:信件咨询

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

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

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

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

方式 2:有偿咨询

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

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

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

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

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

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

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

月刊(第26期):旅途

2024年5月7日 18:54

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

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

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

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》| ★★★☆☆
  • 看完:电影 |《被我弄丢的你》| ★★☆☆☆
  • 重温:剧集 |《庆余年》| ★★★★★

个人工具箱与好物分享

2024年3月29日 19:00
一直都很想写这个话题,因为本人是强烈的数码爱好者同时又是一个执着的工具控,自己尝试过很多新产品,淘汰了一批又一批产品,最终沉淀下来这套较为稳定的工具箱。 而这实际上是一个高度个人化的话题,我们每个人的观点都会受到自己生活经验和视野的影响,我认为优秀的工具或许在别人眼里却并不出色。尽管如此,我仍决定分享出来我目前正在使用的工具箱,希望能够为你带来一些灵感。 > 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,航班信息齐全且更新及时,各种通知的及时性比现场播报还要快。
如果文章对你有帮助,可以考虑进行打赏:
[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/airingursb)

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

2024年3月17日 15:53

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

鸽了两期没有更新,从过年至今为止的假期和周末一直在准备着 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,逃离仿徨

2024年1月14日 18:42

又是一年总结时,自开始写年终总结以来,这已是第六年,年龄也悄无声息地往 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 次):

这里有 5 个 Raycast AI 体验码,可以注册免费使用一个月(每个码只能用一次,仅 Mac):

还有 《月刊(第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期):十年编程之路

2023年11月9日 21:45

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

十年编程之路

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

深得周围小朋友的羡慕,家里小时候是开网吧的,因此我对电脑游戏向来比较熟悉,但未曾设想过以后会走向编程的道路。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期):多任务中的时间管理

2023年10月6日 17:52

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

多任务中的时间管理

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

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

  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期):当下的快乐

2023年8月13日 15:55

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

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

💐 当下的快乐

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

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期):快节奏时代下的短视频

2023年5月3日 17:54

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

🚀 快节奏时代下的短视频

关于周刊选题我有一个 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期):重启人生

2023年4月2日 21:53

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

🎬 本月书影音

  • 在读:小说 |《德米安》 | ★★★★★
  • 在读:小说 |《少年巴比伦》| ★★★★★
  • 在读:文学 |《西方文学史》| ★★★★☆
  • 看完:日剧 |《重启人生》| ★★★★★★
  • 看完:韩剧 |《黑暗荣耀》 | ★★★★★
  • 看完:美剧 |《最后生还者》| ★★★★★
  • 看完:电影 |《情书》| ★★★★★
  • 看完:电影 |《保你平安》| ★★★★☆
  • 在看:动漫 |《钢之炼金术师 FA》 | ★★★★★

这个三月基本没有玩游戏,周末都在看剧,从《最后生还者》到《黑暗荣耀》,再到《重启人生》。一部部来说吧,先从心目中最佳的《重启人生》开始。

如果只打五星已经不能满足我对《重启人生》的溢美之词了,所以这次给了史无前例的六颗星!虽然才 3 月,但我觉得《重启人生》已经是今年的年度最佳了。主角麻美意外去世,得知「下辈子将投胎为大食蚁兽」,若想投胎成人,需要不断重启人生,积德行善。在部重生剧在真正意义上做到了“清新脱俗”,简单谈一谈它的优点吧!

一,细腻的真实感

开篇是麻美的第一轮人生,影片看了四十分钟全是琐碎无聊的日常小事,比如和同事讨论在市公所上班需不需要说“欢迎光临”,给朋友庆生没有准备生日蛋糕、服务员会不会有什么想法,分析爸爸的私房钱藏在哪。整篇故事文本量巨大,充斥着大量的对白,但都在事无巨细地呈现主角的日常琐事,似乎是一些「没用的废话」或者「无聊的思考」,仿佛已经忘了“重生”这件大事。第一集的观感让我感到平淡,甚至有些无聊。但这种细腻的剧本,角色们对生活琐事的碎碎念、以及每个角色特有的小细节,这些闲笔制造真实感,逐渐会让观众接受他们是自己珍贵的朋友,对她们的命运产生共情。

至第三轮时,仿佛看到了自己的影子,虽然每天工作很忙、加班到很晚,但会有好友半夜跑来看你,这种感觉让人觉得甚是幸福。

而至第四轮时,则更充斥着苦涩的现实,如果重生那么多次和最好的朋友、和亲密的家人疏远了,那下辈子即便能够做人还有什么意义呢?在麻美重生四次的百年之后,最想念的仍然是童年时互换贴纸的那个遥远下午,而对于观众而言亦是如此。

冲破文本内部的情绪惯性,抓住人物当下细微的思绪,这种真实感流露出细腻的情感,让我在不知不觉中也视她们为真实的朋友。

二,平视每一个角色,不戏谑任何一种价值观

这里要说到剧中的配角小福,他对自己的音乐才华始终有过高期待,事业不顺,直至离婚,离婚后在 KTV 当服务员,按说很可悲,但编剧对这个人物的塑造始终有种潜台词——任何人都没资格评判或怜悯他的人生。加缪也曾言:“应当想象西西弗是幸福的。

麻美第二轮时曾想过改变小福的人生轨迹,让他的人生变得“更好”一些,但最终她没有去实施,多年后在 KTV 和小福聊天,确认了他对自己当下的生活很满意,确认他洋溢幸福的笑容,麻美对自己的决定如释重负。

平凡并不等于平庸,因为一个人的人生,如果是平凡的人生,至少可以活成自己接受的样子;但是一个平庸的人生,一不留神就会活成自己讨厌的样子。小福是平凡的打工人,但是他至少在努力地、幸福地活着。

这就要说到本剧背后那个更温暖的东西——活着不是快乐,快乐才是活着

三,更温暖而强大的内核

这部剧集给我们呈现了人生的本来面目,但它同时有着一些更丰富、更简单、更强大的东西。

好的作品亦是如此,它不是为了让观众重温现实,而是从现实中创造出一些东西,把一种更强烈的情绪传递给观众。

而时下不少作品充斥着傲慢和对角色、甚至是对观众的戏谑,畸形的时尚造就虚伪。它们把世间一切自然的东西,无论是物质的、情感的、还是伦理的,都当做世仇而加以割舍、抛弃、毁坏,并且认为割舍得越彻底,心灵就越崇高,结果只能导致作品与现实世界的背谬。对肉体和自然人性、人情的无端摧残是不能构成审美愉悦的,由此而获得的沐神福祉的喜悦也是一种畸形的心理病态。

但人在此世间,并非仅仅作为个人而存在,他同时也是独一无二的特殊个体,永远是一个关键而奇妙的点,在这个点上,世界的万千现象纵横交错,充满不可重复的偶然

应了《日常》里的那句话:我们所度过的每一个日常,都是一个个小小奇迹的连续

而我们要做的,只是体会那情感的任性,以把捉自由、探险人性

在这每一个平淡的日常之中,《重启人生》以善良底色书写不说教的故事,赢得喝彩。它承认个体力量的有限,哪怕读档重来,每次也只能进步一点点,但我们仍能通过点滴的小小努力,给自己的人生抛个光。

当然我最喜欢的还是这篇故事最温暖的结局,看到最后会有一种时间都停了、所有人都回来了的感觉,经过了百年来的兜兜转转,终于留住身边最好的人。——与其一个人变成人,不如一起变成鸽子!

PS: 剧组内的氛围也有爱,番外篇值得看一看!

除了《重启人生》,本月看完的《最后生还者》和《黑暗荣耀》也都是满分作品,两者都有着反传统的结局(复仇成功、没有原谅加害者、没有“大义”的介入),总体观感很爽很过瘾。

《最后生还者》还原原作,但适时加入一些轻微的调整和战斗情节的略过,避免剧集沦为游戏那样平铺直叙的公路片。原作原本就是神作,因此忠于原作的剧集也很棒,当然演员表现也棒,乔尔和艾莉的演技令人惊叹。

《黑暗荣耀》相较于它的复仇剧情,我更喜欢它的镜头语言,比如片中不少霸凌情节穿插了第一人称视角,增强了观众的代入感,更易产生共情,让后面的复仇更加爽快;在月色中女主漏出全身伤疤时房间的明暗对比、还有围棋师徒四季榕树的变化之景,也都是片中绝美的镜头。除此之外,剧中部分音效用的也非常不错,值得琢磨一番。

🌺 本月生活点滴

🏸:这个月开始打羽毛球了,自从去年年底疫情肆掠之后就没有打过了,现在重新捡回这项运动,并通过它遇见有意思的人们,是一件幸事。

🚴🏻:这个月开始骑自行车上班,通勤耗时比开车要短一些,并且每天还能省下不少停车费。By the way 电助力车骑在路上就像开挂一样。

👩🏻🍳:在B师傅的教导下学做菜,目前学了电饭煲鸡腿、白灼虾、耗油生菜这种简单的菜肴。(照片就不放了,能吃就行。)

🦆:某个周末的下午在家楼下的河边发了一下午的呆,放空自我,盯着看白鹭是怎么捉鱼的,看流水的形状,感受时间从指尖流过,忽然有那么一瞬间貌似能体会到钓鱼的乐趣了。

👨💻:对工作内容有些迷茫,某个周日找了B前辈吃饭聊了聊,被指明道路之后一切都豁然开朗了,是自己太着眼于当下与急功近利所以才会迷茫,于是回家之后列出了未来7年每一年的职业目标,按这计划一步一步脚踏实地向前走,终有一天能登上顶峰。By the way,那天下午去和朋友试驾了阿维塔,试驾的工作人员给了我很深的印象,虽然是位零几后,但是对工作非常认真负责、富有激情,同时他也对我说到,工作就是要怀着热情且谦卑的态度认真去做,该工作的时候就不要考虑别的,把事情做得漂亮自然就有好结果。

⛺️:组内团建去梧桐山脚下露营,虽然很堵车,但是敞篷在山里派上了用途。By the way,篝火晚会还是蛮有氛围的。

总结:这个三月过得很开心、很充实、很满足,是工作以来最满足的三月了。希望四月是一个繁美丰盛的四月,一见倾心的四月、春意酥怀的四月。💐

月刊(第19期):日记的意义

2023年3月3日 20:52

日记的意义

月底在回顾日记以准备当月的月刊素材时,偶然在 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》| ★★★★★

大厂自研跨端框架技术揭秘

2023年2月25日 23:53

导言

本文将围绕跨端框架技术的主题,分析其技术目标和 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期):逃离社交网络

2023年2月3日 17:51

逃离社交网络

这个月尝试了“逃离社交网络”,有些收获和心得,本期月刊从“动机”和“方法”两个方面来简单分享一下。

0x01 动机

首先是动机,为什么要做这个尝试?

《周刊(第 9 期):高效率到高消耗的现象与反思》中有说过——「移动互联网带来的更多是消耗,同时上网这件事本身,变得焦虑了」。但后来我又想了下,这个描述不够准确,并非是互联网带来的这些负面因素,准确来说应该是互联网中「社交网络」给我们带来了更多的消耗。

这些「消耗」具体表现在以下三个方面:

  1. 表达力被弱化
  2. 专注力被剥夺
  3. 个体价值被遗忘

首先,表达力被弱化

留心观察社交网络上的文体,包括近几个月来网络依次火过的「疯四文学」、「小狗文学」、「小猫文学」、「疯批文学」、「鼠鼠文学」,皆是套语气、套情境的模版体,这些文体现成的几乎所有段子都没有任何有价值的点可以体现其「表达力」。游离于这般环境的社交网站,由此而来,日积月累,表达能力会受到严重损伤,同时造成线上与线下状态分离。线上可能是疯批文学的段子手,各种文体可以信手捏来,但是线下却写不出一篇逻辑自洽、语言流畅的报告。

在《论语言的通货膨胀》一文中作者提到了「语言通货膨胀」这个概念——“币制是交换财富的手段,语言是表达思想和情感的媒介;如同制币与其背后财富的不匹配而生的通胀,语言和语言背后的思想、情感的不匹配,就是语言的通胀。”若某个词语被随意、频繁地使用,该词语的词义将被弱化。此时,该词语所能表达的情感也将被弱化,导致情感表达的不到位,这便是语言的通货膨胀现象。

譬如在以前,我们会用「(笑)」或者「哈哈」来表达快乐的情绪。但现在的网络交流中,「哈」越多,貌似表达的情感越激烈,与之对照来看「哈哈」则显得过于“敷衍”。于是,即便想发「哈哈」也会多凑一些「哈」,避免对方觉得敷衍。这就说语言通货膨胀,在社交网站中,通货膨胀与表达能力的弱化无可避免。

通胀的背后是语言的贫瘠,表达能力的损伤,也是情感的缺失

其次,专注力被剥夺

社交网络会让我们的注意力变得支离破碎,专注力被剥夺。社交网络无时无刻不在产出消息,若自制力不足,则无时无刻不想着接收这些消息。并且现在获取信息的方式简直太多了,很多消息都是被动输入的,哪怕你没有看过《狂飙》,也会清楚地知道主演是谁、有什么梗、未删减的片段是怎么样的;哪怕你不认识某个演员,如果他发生了什么事情,大概率也会从社交网络中接收到。但这些信息有什么意义吗?于我又有何价值?

社交媒体大行其道的一个原因,就是它打破了努力创作有实际价值的作品和吸引到人们注意力之间的正相关关系。相反的,它用浅薄的集体主义式交换取代了永恒的资本主义交换:如果你注意我说了什么,我就会注意你说了什么,不管这话语有无价值。——《深度工作》

而太多的信息造成了信息过载,大脑的容量几乎被这些不断更新的无意义信息填满,如此便造成了消耗。除此之外,当接收的社会消息过多,对事物的敏感度也会降低,情绪变得麻木,不断渴望吃更大的瓜来满足自己在社交网络中的猎奇欲望。

最后,是个人价值被遗忘

互联网成为我们的精神居所,也成为我们与世界连接的媒介,我们处在一个“连接社会”,“人”与“物”之间的关系、乃至是人与人之间的“关系”,都被“连接”所取代。但是在这个网络连接中,我们每一个具体的、有丰富面向的人,在社交网络中只是一个抽象而单薄的收发的节点,个体的价值淹没于社交网络的汪洋中,鲜有人会静心体悟网络一隅中不知名陌生网友的感想,没有情感的透明流量大多被掠夺至宣发素材上。这样的网络,不值得逗留。

0x02 方法

我尝试了一些逃离的方法,选取几个有可行性的进行分享。

方法 1:信息从被动接受,转变成主动获取。

被动接受的大多是垃圾消息或是算法“精心”为你推荐的内容,所以核心一点是转被动为主动,用 RSS 订阅自己关注的周刊、博客、Email、网站、论坛、微博、公众号,每天 1-2 次定时去主动去扫描信息,只阅读你关注的内容。保持好自己的「输入节奏」。是我们在使用互联网,而不是被互联网「利用」。

除此之外需要去减少在「接触社交网络」上的消耗时间,比如关闭朋友圈入口、关闭微信的通知、把微信移除手机首屏。如果真的关心某个朋友,应该是主动点去朋友圈看,而非在票圈 Feed 流刷到了他的消息才去问候。而关闭微信通知推送也是同样的理由,不过找我的人较少,目前看来没有什么影响。

方法 2:培养邮件交流的习惯。

写邮件代替细碎聊天也是一个非常不错的方法,可以沉淀自己日常的想法并锻炼表达力,让自己的关注点回归文字本身,赋予语言与情感最真实、细腻的纹理。并且写信本身也是一件较为庄重的事,可以培养生活里的仪式感。

方法 3:每天为自己留点时间。

最后就是每天要自己留些时间(我是中午和晚上各半小时),这段时间不会有人打扰,也拒绝接受任何消息,专注于自己想做的事——阅读也好,工作也罢。这有利于培养自己专注力,做事时也能够更容易进入心流状态。

本月记录

刚刚过去的一月过得非常充实,把家里人接来深圳过了年,利用假期较为系统地学习了编译原理相关的知识以补充下内功,也借着工作之便实践了一下 babylon。沉下心来阅读,与友人写信,和家人凌晨去看《满江红》,翻出了很久没用的 PC 和精英版的 Xbox 手柄玩了一直想玩的《荒野大镖客 2》——应该是有史以来最棒的一月了。

  • 读完:哲学 |《悉达多》 | ★★★★★
  • 在读:小说 |《少年巴比伦》 | ★★★★★
  • 看完:电影 |《满江红》| ★★★★☆
  • 看完:动画 |《间谍过家家》 | ★★★★★
  • 在看:日剧 |《凪的新生活》 | ★★★★☆
  • 在看:剧集 |《三体》 | ★★★★★
  • 在看:动画 |《中国奇谭》 | ★★★★★
  • 在玩:Switch |《女神异闻录 P5R》| ★★★★★
  • 在玩:Steam |《荒野大镖客 2》| ★★★★★
  • 在玩:PS4 |《双人成行》| ★★★★☆

2022,平安喜乐

2023年1月2日 23:44

转眼间,这已经是第五篇年终总结了。从 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期):心之所向,素履以往

2022年12月25日 20:49

心之所向,素履以往

最近迷上了「徐云流浪中国」的视频,up 主大约 2 日一更,记录自己骑行中国、风餐露宿的流浪生活。

徐云出身农村,两年没有工作过,只带着一辆单车、一个帐篷、一个睡袋、一个防尘布、一张防潮垫、一个折叠式柴火炉、一个打火机、一个高压锅、还有充电宝/手机/运动相机,骑行到了海拔 5000 米川西高原、零下 30 度东北边境。

每天在野外骑行,尽量避开人群,浏览自然风格。夜晚则孤身一人在桥底、道路旁、山沟里、废弃屋内搭帐篷过夜(废弃屋对于他而言简直就是五星级住房了)。并且几乎每顿晚饭都是自己捡木柴生火做饭,除了必要的补充补给之外很少进城住宾馆。

每期视频有近半的时长是做饭的时候对着镜头闲聊。对他而言,这种异步的沟通记录是种奇妙的体验;对我们而言,他成了我们看世界的眼睛。

我把视频推荐给朋友的时候,他们很难理解为什么会有人选择过这种生活。我看了网上的评价,除了鼓励之外,有质疑、有同情也有劝诫。

某期视频中徐云给出了回答:“但路就在那,你得往前走。

这让我想到了刘慈欣的《山》,小说中的主角执迷于登山,他说:“我们之所以登山,因为山就在那里。

小说中的地核文明也和人类的探险史如出一辙,一代又一代去探索外面的世界。无论是好奇心也好,是征服欲也罢,因为它就在那里,所以他们要去。基于一代又一代探险者的前赴后继,地核文明的加加林终于摆脱了暗无天日的地核,看到了灿烂的星空。

心之所向,犹梦何妨?

高中那会儿读《明朝那些事儿》,令我印象深刻的除了王阳明、海瑞、张居正、于谦等当年明月花了较长篇幅介绍的名人之外,还有一个人令我为之动容——徐霞客。在明朝七部曲的文末,当年明月用徐霞客的故事做了全篇的收尾,写得特别好,这里建议读者有空重温下。

当年明月用如此之多的篇幅,讲述一个王朝的兴起和衰落,在终结的时候,却说了徐霞客游记的故事。为什么呢?

此前,我讲过很多东西,很多兴衰起落、很多王侯将相、很多无奈更替,很多风云变幻,但这件东西,我个人认为,是最重要的。

因为我要告诉你,所谓千秋霸业,万古流芳,以及一切的一切,只是粪土。先变成粪,再变成土。

现在你不明白,将来你会明白,将来不明白,就再等将来,如果一辈子都不明白,也行。

而最后讲述的这件东西,它超越上述的一切,至少在我看来。

但这件东西,我想了很久,也无法用准确的语言,或是词句来表达,用最欠揍的话说,是只可意会,不可言传。

然而我终究是不欠揍的,在遍阅群书,却无从开口之后,我终于从一本不起眼,且无甚价值的读物上,找到了这句适合的话。

这是一本台历,一本放在我面前,不知过了多久,却从未翻过,早已过期的台历。

我知道,是上天把这本台历放在了我的桌前,它看着几年来我每天的努力,始终的坚持,它静静地,耐心地等待着终结。

它等待着,在即将结束的那一天,我将翻开这本陪伴我始终,却始终未曾翻开的台历,在上面,有着最后的答案。

我翻开了它,在这本台历上,写着一句连名人是谁都没说明白的名人名言。

是的,这就是我想说的,这就是我想通过徐霞客所表达的,足以藐视所有王侯将相,最完美的结束语:

成功只有一个——按照自己的方式,去度过人生。

《徐霞客游记》中有言:“初四日,兀坐听雪溜竟日。”

反思一下自己这月初四在做些什么,是奔忙着追逐富贵与功名、还是操劳着家庭、忧心着事业?但徐霞客却坐在黄山绝顶,听了一整天的大雪融化声。

徐霞客不是奔着写《徐霞客游记》去旅游的,辛弃疾不是奔着写《稼轩诗集》去救国的。

我们需要的是爱生活,而不是爱生活的意义。如同王小波所说:“我来这个世界,不是为了繁衍后代。而是来看花怎么开,水怎么流。太阳怎么升起,夕阳何时落下。我活在世上,无非想要明白些道理,遇见些有趣的事。生命是一场偶然,我在其中寻找因果。 ”

有首我很喜欢听的歌,叫《路过世界的旅行家》,歌词描述了旅行者的见闻与心境,最后以「那便是你,生而为人曾经存在的印迹」一句词作为结尾。人生是没有意义的,有的只是「一连串的刹那」,那便是我们存在的痕迹。

这一生最重要的是体验和感受,去拥抱美好,度过自己想要的人生。

最后以我崇拜的一位皮克斯设计师在采访中说过的话作为结尾——「成就不在于外在定义,全看是否按自己的意愿在活。

浮游于世,魂魄荧荧。

心之所向,素履以往。

本月记录

  • 看完:韩剧 |《财阀家的小儿子》| ★★★☆☆
  • 在看:日剧 |《凪的新生活》 | ★★★★☆
  • 在看:动画 |《三体》 | ★★☆☆☆
  • 在玩:Switch |《女神异闻录 P5R》| ★★★★★

《财阀》标准爽文穿越剧情,高开低走(不知道算不算高开),综合 3 分吧。如果可以把男女主换下演员可能效果会更好,全剧的演技基本靠爷爷撑起来的 Orz。最后期待一波今晚的大结局。

《凪的新生活》还没看完,并不是很喜欢这种强行洗白的剧情,编剧企图将对他人已然造成的伤害,归因成自身的性格缺陷。不过挺喜欢日剧这种小清新的生活气息。

《三体》动画一言难尽,看完第四集决定弃了,缺点很多就不吐槽了,提一嘴优点——广告营销做的很好。

月刊(第16期):个人信息流分享

2022年12月3日 20:48

周刊暂停了一段时间,发现每周保持更新对我的压力还是有些大的。经过调整之后,发觉 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 渲染流水线——字节码到像素的一生

2022年10月16日 23:49

现代浏览器架构

在开始介绍渲染流水线之前,我们需要先介绍一下 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期):玄学杠杆与异世界小组

2022年7月31日 20:43

玄学杠杆与异世界小组

周末听到一期播客,说的是年轻人的网络占卜: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%

前端开发中的大小写敏感问题

2022年7月24日 23:52

大小写敏感(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 文件系统,默认大小写敏感。
❌
❌