IIFE是立即执行函数表达式（Immediately Invoked Function Expression）的缩写。它是一种特殊的函数调用方式，也是一种用来创建函数作用域的模式。在JavaScript中，IIFE通过将函数用括号包裹，并在后面立即调用它来创建一个函数作用域。这样做的好处是可以在函数内部定义变量和函数，而不会对外部的全局作用域造成污染。这里我最开始对「不会对外部的全局作用域造成污染」的理解有点问题，IIFE内部的声明、赋值等操作都不会影响到全局作用域，是一个完全独立的作用域。抱着想试试是否有这么神奇的用法，我跑了下下面的例子。var a = 'global' // test1 内部局部变量a，不会影响全局变量a function test1() { var a = 1 a = 'test1' console.log(a) } test1() console.log(a) // test2 内部没有var，会直接修改全局变量a function test2() { a = 'test2' console.log(a) } test2() console.log(a) // test3 内部局部变量a，不会影响全局变量a ;(function () { var a = 'test3' console.log(a) })() console.log(a) // test4 内部没有var，会直接修改全局变量a ;(function () { a = 'test4' console.log(a) })() console.log(a)运行结果test1 global test2 test2 test3 test2 test4 test4其实从结果可以看出来，IIFE从作用域隔离的角度上来看其实和普通函数闭包没什么区别（其实本身也差不多是一个东西）。那么IIFE的作用到底是什么呢？其实要从比较实际的场景出发，在某次掘金冲浪中发现了这样一篇文章，解释了IIFE在没有 CommonJS 等技术时是如何实现模块化的。原文链接：从构建产物洞悉模块化原理以这个下面的HTML为例 其中 index.js 和 list.js 由 A 负责，home.js 由 B 负责。A 在 index.js 中声明var name = 'foo'并在 list.js 中使用console.log(name)结果却得到 name 变量变为了一个函数，而这是因为 B 在 home.js 中将 name 声明为了一个函数function name(){ //... }而这里如果 B 能够以 IIFE的方式来编写 home.js 则不会出现这种情况。因为 IIFE 在执行这段JS时就会立即形成闭包来进行隔离保护，甚至都不需要一个具名的方法来运行。虽然这种方式在现在看来还存在着很多提升空间，但在当时的环境下解决了全局变量污染的问题也算是给予了 JS 丰富的插件生态的发展空间（个人认为）。
随着2025H1考核自评提交，猛然发现今年已经过了一半。与去年下半年相比是截然不同的体验，有一种现象感觉在生活中的各个方面同时出现了——瓶颈。工作年后的一两个月我主要负责一些平台的日常需求，节奏非常稳定。慢慢习惯下来后也开始有了更多的时间在个人上，重拾了之前自己的一些小项目、用新的技术栈翻新了之前的代码。甚至和朋友（还是上篇博客提到的小Y）约定好了五一第二次日本之旅。AI像一阵风，吹完他就走，这样的节奏，谁都无可奈何~AI的风还是刮到了我们部门，我也被抓壮丁去负责了平台侧接入AI的重点项目。又是典中典之时间紧任务重、“老板要看效果”等。从3月底的某一个晚上开始，十点的工位开始频繁出现我的身影。没日没夜的加班开始了，4月初开始的一周半左右的迭代周期，到4月底的MVP版本，再到5、6月份的周迭代。其中在5月份左右，随着项目度过MVP版本，项目开始走正规一些的管理流程，我也被leader指定为了前端负责人。作为一个初出茅庐的校招生，刚开始我并不明白为什么要推我去做负责人。平时赶需求就已经占尽了我十分气力，又何来精力去协调我们组内的前辈们。虽然后来我才幡然醒悟是也许是组长有意为之想锻炼锻炼我，但对于当时的我来说险些成为了压死骆驼的稻草。好在关关难过关关过，我用更多的时间去弥补了经验的不足。项目度过了相对野蛮的发展时期，我也没有成为被压死的骆驼。一切都在朝着好的方向发展，而在过去的暗无天日的Q2，浓浓的班味还是深深地影响到了我生活的大多数时候。减肥23~24年，我通过跑步减下了差不多26斤（84kg~71kg）。这件事情真的让我改变了许多。运动与生活相结合带来的正反馈可以辐射到生活中非常多地方，带给我很多自信心和动力。让我在面对挑战和困难的时候都能更加从容一些。但随着加班日益增多、生活节奏被打乱，虽然自己还在想方设法保持运动量，可从指标上却一点看不出来——我的体重不降反增。每次上称都会对眼前的数字感到惊讶。虽然想去努力调整，但更多是心有余而力不足。疲惫下班后路过甜品店，在恶魔递出奶油点心前常常失守心神，出卖健康得到短暂的愉快与深夜的懊悔。日常学习去年入职时给自己定下了长期学习的目标，希望能形成习惯与运动一起提升身心的生活品质。主要的方向有两个：前端技术学习日语学习前者主要是靠业余时间的编码与探索进行经验积累，辅以一些经典文章与书籍。后者则主要是通过标日和多邻国等教材和软件来学习，利用碎片化时间来进行单词的背。在刚入职那一阵子感觉效果非常不错，通过订阅「Javascript Weekly」等一些优质频道进行视野拓展。再加上周末也确实会鼓捣一些小东西，感觉编码水平是有在稳步提升。日语的学习则与日常相结合，起床后、运动后都会背下单词，配合一些教材和多邻国。感觉也能够脱离番剧说两句日语了。但伴随着可支配时间逐渐减少但心中一直有着不想放弃的执念，我的学习虽然仍在继续但变得有些畸形。我开始使用碎片化的时间去看一些前端知识小短文（掘金等），面对一些可能有点争议的观点我也没有时间去钻研，看到一些来源不明的文章有时也懒得去鉴别是否正确。日语学习方面感觉更是一塌糊涂，已经完全沦为钻石等级的奴隶，每天打卡只为了留在钻石等级。背单词也是打卡后草草下场。寻求突破也许这只是职场对于我这种小白的第一个考验：如何平衡工作与生活。职场试图叫我跳出学生时代的舒适圈，既然我已经意识到了，那就该找到突破口来度过这个考验。在某些地方可能我会做出让步，但更多的方面我希望找到适合自己的方式来平衡、充实、丰富自己的生活。
在大学的尾声，终于有机会以毕业旅行的名义去了一趟日本游玩。本想在回国后就写一些文字记录一下，但临近毕业的疯狂还是没能让我拿出键盘。直到工作后在几个加班的夜晚，总能回想起当时的一些趣事，便觉得还是有必要将这10天的短暂时光稍微记录一下。不得不说，这一次的旅行真的是充满幸运，且听我娓娓道来。这篇文章的诞生可以说是几经周折。本来想写一篇事无巨细的记录性游记，写到 4000 字（约 30% 进度）左右的时候因为一次操作失误导致草稿丢失了。可能我的流水账文章让博客之神感到不满，所以把我的稿件退回了。既然如此我也不花过多笔墨去介绍一些细枝末节的小细节了。10 天的冒险在 2024.5.27 日，我与朋友小 Y 从深圳出发。旅途按照地区主要分为三个部分东京（3 天）东京周边：镰仓、富士山（2 天）关西：大阪、京都（3 天）一帆风顺の东京东京是我们此行的第一个目的地，我们的存档点（酒店）订在了比较便利的池袋。距离宇宙中心新宿只用 4 站电车。只要到了新宿之后一切交通问题都可以迎刃而解。而潮流中心涩谷距离新宿也只需要 2 站。不过银座等东京 CBD 地段就稍远一些了。经过规划，我们的游玩顺序也是由近至远。DAY 1第一天属于新宿与涩谷这两个双子星。上午逛了下新宿御苑，感受了下日本的皇家庭园。由于没有什么艺术细菌，只是走马观花一般地逛了一圈。倒是小 Y 突然想起来言叶之庭里好像有这个新宿御苑的戏份，于是又屁颠屁颠地去打了个卡。（其实也不知道是不是那个地方）[photos]新宿御苑,https://cdn.exia.xyz/img/blog/632/IMG_20240530_113440.jpg|疑似打卡点,https://cdn.exia.xyz/img/blog/632/IMG_20240530_114208.jpg[/photos]吃过午饭后我们便开始了街溜子之旅，一路从新宿逛到涩谷（并非徒步）。作为两个对潮流元素不那么感兴趣的小伙子，新宿与涉谷纯粹是来打打卡、见见世面，逛的最多的还是各种中古店。[photos]波奇酱,https://cdn.exia.xyz/img/blog/632/IMG_20240530_144036.jpg|M 记自拍,https://cdn.exia.xyz/img/blog/632/IMG_20240530_145355.jpg|白天还没什么人的歌舞伎一番街,https://cdn.exia.xyz/img/blog/632/IMG_20240530_151425.jpg|传说中的十字路口,https://cdn.exia.xyz/img/blog/632/IMG_20240530_155901.jpg[/photos]DAY 2第二天我们直奔二次元圣地秋叶原进行一个朝圣。从电车站一出来就能看到在命运石之门中经常出现的场景：车站本身以及ラジオ会館[photos]车站大门,https://cdn.exia.xyz/img/blog/632/IMG_20240531_105507.jpg|收音机会馆说是,https://cdn.exia.xyz/img/blog/632/IMG_20240531_105713.jpg[/photos]说到二次元，我也只能算是追追番，闲的时候玩玩二游。但是也只到此为止了，没有疯狂地吃谷、没有成堆的手办山，更没有魔怔的精神状态。可以说是一个精神健康且心智健全的阳光小伙。不过这也和我的生长环境脱不了关系，从小生活在二次元氛围并不浓郁的深圳。听去了广州念大学的同学说，当地几个高中的 ACG 社团还会联合举办活动，更是有着远近闻名的萤火虫漫展。在某次机缘巧合下被该同学拐去了大名鼎鼎的动漫星城，狠狠刷新了我对老广的二次元浓度的认知。从那之后我也更加期待本次的秋叶原之旅。[photos]经常在网图中出现的场景,https://cdn.exia.xyz/img/blog/632/IMG_20240531_150336.jpg|走出国门的米哈游,https://cdn.exia.xyz/img/blog/632/IMG_20240531_111446.jpg|二手书店中的专业书籍,https://cdn.exia.xyz/img/blog/632/IMG_20240531_154844.jpg[/photos]逛完一天下来，可以感受到秋叶原的二次元气息与国内有所不同。国内的二次元文化的氛围其实已经非常优秀了，广泛的受众与愈发成熟的商业模式能够支持其国内二次元的快速发展。日本作为发源地，更像是将二次元文化融入了生活，在秋叶原看到的从业者们大多数都是真心热爱并且对二次元文化了解也比较深。国内由于发展速度比较迅猛、市场也日益壮大，不少看到机会的圈外人也进来发展。所以在国内有时能看到一些看似精美实则牛头不对马嘴的谷子存在（或者干脆就是粗制滥造纯蹭），但是在秋叶原则是看不到这种情况。在秋叶原，二次元就是生活。以上的话纯粹是我一个二次元半门外汉的观点，可能会比较肤浅，仅供参考。晚上 7 点，我们发动了对浅草寺的奇袭行动。行动很成功，我们享受了一段人流量较少的“独享”时光。虽然才 7 点，但商铺都关上了门，给我们这些晚到的游客赠送了免费的闭门羹。[photos]下班下班,https://cdn.exia.xyz/img/blog/632/IMG_20240531_195545.jpg|远处的晴空塔,https://cdn.exia.xyz/img/blog/632/IMG_20240531_195917.jpg|不知道叫什么的塔,https://cdn.exia.xyz/img/blog/632/IMG_20240531_200217.jpg|只有卖御守的没下班,https://cdn.exia.xyz/img/blog/632/IMG_20240531_201812.jpg[/photos]在景区内看到了求签的地方，索性试试手气。如果是吉那就最好，是凶也没关系，日本的神管不到咱。抱着这样的心态一抽，没想到还真是一发入魂。「凶」虽然抱着无所谓的态度，但还是不愿接受这一事实。必须抽出保底！在我的第二次猛攻之下，凶签也被我的气势镇住了，我如愿得到了一个「吉」。[photos]参拜,https://cdn.exia.xyz/img/blog/632/IMG_20240531_202747.jpg|凶,https://cdn.exia.xyz/img/blog/632/IMG_20240531_201226.jpg|吉,https://cdn.exia.xyz/img/blog/632/IMG_20240531_201508.jpg|听说绑起来可以解凶签,https://cdn.exia.xyz/img/blog/632/IMG_20240531_201628.jpg[/photos]逛完浅草寺，趁着夜色我们也顺便逛了下附近的街区，感受了一下东京的寂静角落。[photos]你瞅啥,https://cdn.exia.xyz/img/blog/632/IMG_20240531_195959.jpg|夜色街头,https://cdn.exia.xyz/img/blog/632/IMG_20240531_204051.jpg|夜色街头,https://cdn.exia.xyz/img/blog/632/IMG_20240531_204553.jpg[/photos]DAY 3终于，东京的最后一站我们来到了繁华的 CBD 地区——银座。稍微逛了一下发现这里疑似有点过于上流了。正巧小 Y 有想买衣服的需求，又正巧这里有一栋 12 层楼的优衣库，索性就直接进攻。别人买奢侈品大包小包，我们买优衣库也是大包小包，效果都一样！（无谓的虚荣心不可取）[photos]银座大街,https://cdn.exia.xyz/img/blog/632/IMG_20240601_122655.jpg|在国内好久没见过这种风格的海报了,https://cdn.exia.xyz/img/blog/632/IMG_20240601_125159.jpg|步行者天国,https://cdn.exia.xyz/img/blog/632/IMG_20240601_152037.jpg|也是消费上了,https://cdn.exia.xyz/img/blog/632/wx_camera_1717222407017.jpg[/photos]心满意足地购完物之后，又路过一家朝日生啤的快闪店，小 Y 来自山东，也算半个好酒之人。我也恰好有些兴趣，便排起了队。没过多久便轮到了我们，到门口时才发现原来里面是介绍朝日生啤的生产流程的快闪活动，主要是播放一个影片，有点类似于国内有些游乐场所的 5D 电影。影片放完后可以现场来上一杯产地当日直送的 Asahi suuuuuuper Drrrrrrry。整个影片大约 5 分钟，略带科幻元素。但是你别说，看完之后喝上一口还真感觉上了点 buff。[photos]小 3 层的店面,https://cdn.exia.xyz/img/blog/632/IMG_20240601_155654.jpg|正就是 SUPER DRY！,https://cdn.exia.xyz/img/blog/632/IMG_20240601_160122.jpg|大叔喝一杯吗？,https://cdn.exia.xyz/img/blog/632/IMG_20240601_162933.jpg[/photos]差不多也到了傍晚，到了此行东京的收尾节目——落日飞车。不过这里的落日飞车并不像奈良那种单人在山上坐的轨道车。而是在东京范围内运营的 SKY HOP BUS 敞篷观光大巴。大巴有着不同线路，会分别带着兜一圈各种标志性景点，也算是带着我们把没有逛到的地方都过一遍了。这个大巴全天都有运营，而唯独傍晚的最后一班由于能接着夕阳欣赏城市美景而被称为「落日飞车」。由于没有找到线上购票入口，所以我们一早去到了东京站前的三菱大厦门口排队购票，那么现在就可以开始享受了！[photos]出发！,https://cdn.exia.xyz/img/blog/632/IMG_20240601_180810.jpg|仰望东京塔,https://cdn.exia.xyz/img/blog/632/IMG_20240601_182400.jpg|首都高东京塔与黄昏,https://cdn.exia.xyz/img/blog/632/IMG_20240601_185739.jpg|结束后的东京站前,https://cdn.exia.xyz/img/blog/632/IMG_20240601_191949.jpg[/photos]悲喜の东京周边由于小 Y 当时对《孤独摇滚》兴趣颇丰，并且我也对镰仓这个充满青春感的地方大感兴趣。所以和小 Y 一拍即合计划来一场湘南一日游。而说到关东当然还有一个不得不去的景点——富士山。DAY 4乘着湘南新宿线来到藤泽，换乘传说中的江之电。当海岸线出现在视野中时，感觉临近毕业带来的一些压力被一扫而空，又有无数思绪涌上心头。没等我反应过来，眼泪就先一步流了出来。[photos]海だ,https://cdn.exia.xyz/img/blog/632/VID_20240602_112740.gif|海岸线,https://cdn.exia.xyz/img/blog/632/VID_20240602_113018.gif[/photos]除了镰仓高校前站因为「灌篮高手」以及各类风景旅游博主的宣传而人山人海，其他的地方就和普通的日本小镇差不多了。我不知道该如何形容这样一座海边小镇，虽然可能有个人滤镜的加持，但他给我的感觉就是那么宁静而美好，就像动漫里一样。[photos]远处的江之岛,https://cdn.exia.xyz/img/blog/632/IMG_20240602_113913.jpg|依山靠海的小镇,https://cdn.exia.xyz/img/blog/632/IMG_20240602_115243.jpg[/photos]沿着海岸线感受了一下咸湿的空气，用完午餐后我们便前往江之岛巡礼。但是天有不测风云，越是深入江之岛内，天气就越发阴沉仿佛里面有什么游戏的大 BOSS 需要加载地图特效一般。还好小 Y 的「孤独摇滚」巡礼并不需要过度深入江之岛，稍微爬两步就已经打卡完毕。此时也已经落起了稀稀疏疏的雨滴。本来计划在江之岛上看着落日吃晚饭的，现在看来是泡汤了。但是来都来了，我和小 Y 还是选择硬着头皮往前冲。我们就这么冒着雨来了一场不一样的江之岛之旅。[photos]完全什么都看不到思密达,https://cdn.exia.xyz/img/blog/632/IMG_20240602_170310.jpg|听说敲这口钟就能获得爱情,https://cdn.exia.xyz/img/blog/632/IMG_20240602_164958.jpg[/photos]六点半好不容易逃离了江之岛回到藤泽，小 Y 本来还想去尝一下车站附近一家评价很好的天妇罗店。到店后却发现一家人满为患，店员阿姨拿着牌子告诉我们估计要等非常久，只能遗憾离场了。回到酒店，遭遇接连打击的我们两个瘫软在床上无法动弹。不过最后还是为了明天的富士山之旅，爬起来去附近的松屋对付了一口。顺便祈祷着明天的富士山天气可以一切正常。DAY 5虽然前一天晚上祈祷着一切正常，但是当我们乘坐巴士来到浅间神社时，富士山还是被笼罩在一阵云雾之中，羞于露出芳容。辗转到炒鸡出片的罗森，天气却还是不见好转，甚至更加糟糕。[photos]山呢,https://cdn.exia.xyz/img/blog/632/IMG_20240603_102257.jpg|别躲了😭,https://cdn.exia.xyz/img/blog/632/IMG_20240603_121855.jpg[/photos]但是这也是没有办法的事，在来之前我们就在网上了解到六月是一年当中最难看到富士山的月份。历史上来看六月平均只有两三天可以看到完整的富士山。头铁的我们那管得上这么多，万一我们就是那两三天中的一天呢。别看我这样，游戏里抽卡我也还算是比较欧的。但事实狠狠打了，哦不对，抽了我们的脸。但是我们还不服输，罗森看不到，总有一个角度可以看到。从网络监视器上发现河口湖的一座小山上有个观景台貌似可以看到富士山全貌，于是我们又赶到河口湖。上山可以乘坐缆车或者徒步上山。因为觉得没必要花这冤枉钱，我们选择了爬上山。本以为是台阶道，没想到却是野路。并且雨滴像是要将我们赶下山一般突然冒了出来。但是来都来了，怎么能就这么草草下播？望着没有一眼看不到头的山路，我们再一次顶着雨向上进发。艰难地爬了 40 分钟后，我们终于抵达了山顶。但迎接我们的还是不愿露面的富士山。并且由于下雨，山路下山异常难走，缆车也停止运营了——我们被困在了山上。即使我和小 Y 都属于乐天派选手，此刻都不禁有些心灰意冷。大概半个小时后雨渐渐停了，但雨后的泥巴路我和小 Y 也不敢轻易去攻略。最终还是没法逃离花钱做缆车的命运。（单程相比往返贵多了！😭）[photos]命中注定的车票,https://cdn.exia.xyz/img/blog/632/IMG_20240603_150421.jpg|太阳出来了富士山呢,https://cdn.exia.xyz/img/blog/632/IMG_20240603_151229.jpg[/photos]下山后，我们从河口湖一路溜达至山中湖。到达山中湖已经是五点左右，天气又风云突变开始刮起寒风。天空灰灰的，整个视野范围内看不见一个活人。冷得我和小 Y 直打哆嗦，加上走了一天，肚子也打起了响雷。再这样下去会死的（bushi）虽然没有这么夸张，但还是想立马找到一家温暖的店铺吃上一份热气腾腾的火锅。但这一次上天眷顾了我们，湖边就有一家做锅物的餐厅。到店后发现这是一家装潢风格相对传统小店，虽然不是榻榻米类型的座位，但也是席地而坐。店内暖暖的，只有一桌客人在用餐，零星的谈笑声将店内的温馨氛围烘托到了极点。我和小 Y 也放松了下来。酒足饭饱后，我惬意地支着手臂坐着，突然发现眼前的一切都变亮了不少。我回头看去，发现之前笼罩着的阴云已经不在了，阳光正洒在湖面上。看上去一副风和日丽的景象。小 Y 也发现了这一点，疯狂扒着碗里剩下的最后一点饭菜，收拾行李起身买单。一出门发现湖面映射着夕阳的余晖，远处的云彩也染上了黄昏的色彩。难道说...回头望去，我和小 Y 同时被震惊住了。拼尽一天全力无法战胜的云雾消散了，露出了在这个季节本不该盖着雪顶的富士山。是因为老天不忍看着我们遗憾离场吗？是因为我们花了一天时间和富士山混熟了所以富士山愿意出来见见我们吗？我们已经思考不了这么多了，就当它全是吧。[photos]好不容易,https://cdn.exia.xyz/img/blog/632/IMG_20240603_181625.jpg|像油画一样的天空,https://cdn.exia.xyz/img/blog/632/IMG_20240603_182944.jpg|返程的大巴车上，富士山更加完整了,https://cdn.exia.xyz/img/blog/632/IMG_20240603_184207.jpg[/photos]奇袭关西在指定旅行计划的初期对于如何从东京抵达大阪一直困扰了我和小 Y 许久。新干线对于我们来说太贵，飞机我在国内都坐不明白更别说在日本坐飞机了，可能值机都是问题。好在小 Y 发现了一个非常规的特种兵解决方案——夜行大巴。大巴由晚上 23:05 从バスタ新宿4楼出发，一路开到第二天早上 9:05 抵达 WILLER 大阪梅田巴士总站。与国内早些年的卧铺大巴定位相似。日本的夜行大巴的座位是经过特殊设计的睡眠仓样式，提供了相对私密的休息环境。先不谈是否舒适，单单是他能够以比一晚酒店还便宜的价格同时还能同时解决从东京到大阪的移动问题就已经让我们没有再考虑其他方案。就这样，在从富士山回到新宿后，我们闲逛了一会后便直接上楼乘坐上了前往大阪的大巴车。[photos]下雨的宇宙中心,https://cdn.exia.xyz/img/blog/632/IMG_20240603_210632.jpg|说明书,https://cdn.exia.xyz/img/blog/632/IMG_20240603_230500.jpg|其实还是略显局促,https://cdn.exia.xyz/img/blog/632/IMG_20240603_230705.jpg[/photos]DAY 6早上九点，一夜没怎么睡好的我和小 Y 顶着一头油拖着行李离开了巴士总站。虽然座椅还算舒适，但要那么一动不动睡一晚上确实是有点难度，被迫体验了一下传说中的高效睡眠法。实在是受不了了的我们决定先去酒店寄存一下行李，再轻装上阵稍微逛一逛大阪市区。[photos]笔直的道路,https://cdn.exia.xyz/img/blog/632/IMG_20240604_103211.jpg|天气不错,https://cdn.exia.xyz/img/blog/632/IMG_20240604_110059.jpg|道顿堀打卡点,https://cdn.exia.xyz/img/blog/632/IMG_20240604_111448.jpg|心斋桥,https://cdn.exia.xyz/img/blog/632/IMG_20240604_111656.jpg|回头就是格力高,https://cdn.exia.xyz/img/blog/632/IMG_20240604_111717.jpg[/photos]到了下午三点，终于到了酒店 check in 时间，受不了一点的我们立刻杀回了酒店洗澡睡上了一觉。约 6 点起床过后下楼觅食，本想再去逛逛夜晚的道顿堀心斋桥，但不知道为什么对热闹的街区失去了兴趣。想了一下还是去拜访一下天守阁。夜色下的大阪城公园略显宁静，就像是我家小区楼下的公园一般。从城门出来后，看到有一个小哥哥在跑步，突然想起我有个跑步点亮全球地图的梦想，也即兴跑了小一公里。[photos]夜色街头,https://cdn.exia.xyz/img/blog/632/IMG_20240604_210028.jpg|天守阁,https://cdn.exia.xyz/img/blog/632/IMG_20240604_214240.jpg|散发着奇妙气息的电话亭,https://cdn.exia.xyz/img/blog/632/IMG_20240604_215626.jpg|打卡大阪,https://cdn.exia.xyz/img/blog/632/Screenshot_2024-06-04-22-17-49-244_com.mi.health.jpg[/photos]跑完休息还遇到了一只可爱的小🐶，一看到我们就想我们屁颠屁颠地小跑过来。等到主人过来叫走才肯离开。带着好心情回到酒店后，稍微准备了下明天的行程便再次沉沉睡去。DAY 7望着京阪铁路的沿途风景，京都一日游开始了。从地下车站走出到地面，感觉到一股历史的厚重感扑面而来，好吧夸张了一点但确实和东京以及大阪不同。没有林立的高楼，没有繁忙的人群，来往的人们都透露着轻松惬意的生活气息。一天的时间，从清水寺一路逛到伏见稻荷大社，路过二年坂三年坂、花见小道等景点。感觉和东京就像是两个日本。[photos]清水寺,https://cdn.exia.xyz/img/blog/632/IMG_20240605_114431.jpg|疑似鸭江,https://cdn.exia.xyz/img/blog/632/IMG_20240605_141140.jpg|远处的城市,https://cdn.exia.xyz/img/blog/632/IMG_20240605_171327.jpg|傍晚的伏见稻荷站,https://cdn.exia.xyz/img/blog/632/IMG_20240605_175536.jpg[/photos]回到大阪后，感受了一天惬意氛围后意犹未尽的我们又来到了夜晚的道顿堀，感受了下年轻的味道。果然夜晚才是道顿堀与心斋桥的主场。兴奋的我们逛了个不停，猛然发现已经到了 9 点而我们都还没吃饭。但是大部分店铺九点半左右就打烊了的同时门外居然还排了不少人。就这么边走边找到了 10 点钟还没吃上饭，不过也找到了一家营业到凌晨且评分也很高的大阪烧店，门外虽然也排着不少人，但我们已经快要饿到无法思考了。只能选择这家店，直到 11 点才吃上饭，好在味道确实对得起评价。[photos]人多到手抖,https://cdn.exia.xyz/img/blog/632/IMG_20240605_210037.jpg|再访格力高,https://cdn.exia.xyz/img/blog/632/IMG_20240605_210115.jpg|终于吃上了晚饭,https://cdn.exia.xyz/img/blog/632/IMG_20240605_230604.jpg[/photos]DAY 8这一天专门留给了购物，在道顿堀与心斋桥买了不少家伙才心满意足地结束了这一次的旅行。浓厚的音乐氛围噼里啪啦的流水账说完了，这趟 10 天的旅途确实让我很好的体会到了日本这个国家的风土人情。不跟团的一大好处就是可以随心所欲的探索，也能感受到一些跟团时感受不到的东西。例如日本的音乐氛围。在抵达东京的第二天，结束了一天行程的我们返回池袋时，就遇上了正在池袋 Animate 进行新曲宣发的辻詩音小姐的现场 live。当时在台上的是个三人小乐队，由带着面具的主唱+键盘+小提琴手组成。当时只是抱着兴趣听一听，但是没想到越听越耳熟，回到酒店一查才发现原来是听过的歌手辻詩音换了个马甲租了个新乐队开始活动了。在我心中感觉辻詩音已经算是比较有名气的歌手了，但也还是会这样到现场出 Live，明明现场都没多少人在听，但还是很投入地去演唱了。这是第一次感受到日本随处可见的音乐气息。第二次是在银座等观光大巴出发前，在附近闲逛时听到不远处传来了歌声，凑近发现又是个 live。这次的虽然不认识，但一搜发现也是给某个番剧制作过 OP 的歌手。在这个类似小音乐节的活动客串嘉宾。还有一次，在新宿站等夜行大巴的时候，在铁路的天桥底下碰到了正在路演为自己的个人 live 拉票的一个小姐姐。[photos]传出强劲音乐的animate店,https://cdn.exia.xyz/img/blog/632/IMG_20240530_191553.jpg|银座 live,https://cdn.exia.xyz/img/blog/632/image-20250323224244832.png|立石步小姐姐,https://cdn.exia.xyz/img/blog/632/image-20250323224320128.png[/photos]毫不夸张的说，日本真的是一个随地大小 live 的国家。短短几天我就遇到了3 次现场的演出，而在国内搜刮了我出生到现在 20 多年的记忆也鲜有印象。记忆中的街头演唱基本上都是求捐款的，而不像在日本遇到的这几位是将音乐视为事业来对待的。再结合日本各种校园乐队浪潮以及各种音乐番剧的爆火，日本的音乐的快速发展除了本身语言发音等因素之外，也确实是有其音乐氛围的原因在的。总体感受由于我缺少相关知识，所以只谈谈我的主观感受，作为游客我能体会到的文化也有限。所以下面的观点都只是建立在我这几日的旅行所感之上。日本的服务业的质量真的很高，去过的店里面，服务员都是彬彬有礼。在车站等地方咨询服务人员时也都非常有温度。进门有热情问好，离开有礼貌送别。也难怪日本一直被称为最适合旅行的国家，真的有一种非常舒适的感觉。其次感受比较深的时日本人的距离感。尤其是在饭店与电车上，大家都尽量不去打扰别人，整个社会仿佛都有这无言的默契在。不知道是不是因为从小看日漫的原因，虽然看的不多，但还是在我心中留下了一些痕迹。在小时候还算比较活泼好动，但随着年纪的增加，慢慢向着日本这种思想发展了。不管做什么都会想着不要打扰到别人，也不希望有人打扰我，很多时候都只想一个人待着。以至于这次到了日本发现大家真的都是这个样子后，产生了一种我就是在这种环境长大的，或者我能很快融入这种环境的错觉。当然这都是脑袋一热的错觉，日本社会还有很多现象是我无法适应无法理解的，各种条条框框的社会规则已经停滞不前的各行各业。日本是一个理想的旅游去处，但也仅此而已了。警惕文化入侵总的来说，这次的旅程还是非常幸运且圆满的，希望有机会能再去玩一玩吧。
自从2022下半年去腾讯实习后，就好久没有对博客进行过维护了。虽然有时会心血来潮迁移博客到更“好”的节点，结果却导致了更多问题出现。神经大条的我终于在这两天发现了一些端倪，这才动手处理了一下。虽然自认为对 typecho 这个系统已经足够了解，但是遇到问题排查起来还是满头大汗。下面是本次拔草发现的所有问题。访问奇慢无比评论大概率失败评论邮件通知不翼而飞各类数据库表问题层出不穷这是从哪里冒出来的冒充动态博客的静态网站吗？在发现了相关问题后，我便着手开始对我这菠萝菠萝的博客进行抢救了。下面是部分原因分析与处理方案👇🏻访问速度慢之前自己琢磨网络架构，把数据库都集中到了一个服务器节点统一管理。其他子系统则是通过远程连接来访问数据库。当前博客所在的服务器与数据库服务器的连接速度简直堪忧。导致 typecho 从数据库拉取数据花费的时间都非常长。当 typecho 终于渲染出页面传给用户浏览器后，等得不耐烦的浏览器又遇到了两只拦路虎：两张大小分别为 16.4mb 和 9.6mb 的图片。这两张图片在消耗流量的同时相比也消耗了不少游客的心智。虽然不知道我当时为什么要设置这么大的背景图片和头图，但是我这么做一定有我自己的道理吧！最后将博客与数据库放在了同一台服务器上并把图片换成了普通大小后，博客的访问速度也算是恢复了正常水平。评论大概率失败评论相关问题则是由各类插件与数据库连接异常引起的。在一条评论从游客的浏览器发出后，它需要经过以下两个插件的把守才可以抵达数据库SmartSpam 反垃圾评论插件CommentToMail 邮件提醒插件首先是犯下傲慢之罪的反垃圾评论插件，仗着自己是管理员信任的第一大关，掌管一条评论是通往垃圾箱还是数据库的生死大权，便为所欲为。在测试中反垃圾评论插件经常发生各种误判情况，明明是不包含敏感词的正常评论内容，却被标记为垃圾评论；明明是一个普通的游客邮箱，却提示被管理员屏蔽。工作态度之差劲令人瞠目结舌，简直难堪大用。其次是犯下其次之罪的邮件提醒插件，工作中更是充满各种虚报误报。理想情况下在激活插件时，它会在服务器中建立一个表，用于记录评论邮件发送状态，在每次评论时都去更新表的数据。实际情况是在激活插件时，它 try 了一下在数据库中建表，并没有成功。于是他做出了一个违背祖宗的决定，隐瞒建表失败的事实，上书禀报建表一切顺利。最后在实际评论时露出了马脚。这也导致了评论通知邮件的不翼而飞。鉴于这两位的工作表现与工作态度，我决定奖励它们永久假期。邮件提示插件也换成了 ethanzhao2001/Mailer-Typecho。真实情况可能是 typecho 更新后，老版本插件的兼容性问题。以及迁移博客后，没有正确设置文件权限的问题。其他在拔草之余，我也访问了一下友链中的朋友们。看到了不少朋友的近况，也看到不少朋友的博客也渐渐停止了更新。甚至有很多已经无法访问。虽然博客的逝去已经是必然的趋势，但是当离别真正来临还是免不了落泪。在这次访问中发现下面的这些博客已经无法访问了，便将他们暂时放到了友链页面的失效折叠列表中。如果后续有恢复的话，欢迎联系我添加回来。暮去长空海了个螺小凡梦WitheredstarMZRME‘S石青舟Funix's blog东马的白色相簿Y2Nk4Troy的星球南蛰的藤球球metheno小代理の记事簿客栈一新梦游计
2023年对我来说是毫无疑问的丰收年。今年有了太多突破与成长，做了很多从未做过的事，也向着更好的自己迈出了一大步。今年做了什么“闭门造车”的上半年在23年的年初，我开始学习一些前后端新技术，尝试了很多新的技术栈。在前端，印象比较深刻的有ValtioTailwindcssReact Query (Tanstack/Query)这也代表了前端比较重要的3个方面：状态管理、样式、请求。在这其中也尝试了不少其他同类的相关工具，比如状态管理还玩了玩Jotai, zustand等。请求库这边也试了试ahook的useRequest和alova等。样式这边也是鼓捣了不少组件库与插件。后端这边主要是学习了一些基础的东西，比如SequelizeExpressMongoDBRedis主要是一些后端框架和数据库相关。但是光是敲一些简单的例子感觉还是不太够，于是想起了我高中走神时琢磨的一些个人网络的构想。以一个SSO为中心建立起一套完善的应用系统，基础形态包含如下组件。SSO动态配置系统 - 用于接入到其他组件提供动态配置能力消息系统 - 实现多渠道的通知能力（邮件，短信...）服务器状态管理 - 类似于探针一类的东西重构博客各种小应用...虽然做出来大概率就是自己一个人用，根本犯不上这么复杂，但还是忍不住动手开始敲了。吭哧吭哧边学边敲，敲了三四个月，总算是把基础的SSO和动态配置系统肝出来了。这期间除了从技术的角度研究，也不断尝试用产品的角度去思考怎么样才能让我的0 IP应用变得相对更好用一些，也算是有了不少的收获。秋招小子大三下学期，计算机专业的带学生不可避免地迎来残酷的秋招。回想起我的秋招经历，做了许多努力，也做了许多错误的决定。6~10月的这段时间里，大多数时间都在埋头看技术相关书籍，刷算法题，试着慢慢完善我的知识体系。但事与愿违，我的努力并没有收到相应的成效，因为我做了一个错误的决定。与其他秋招小子不同，我没有去看八股文，而是回到红宝书等相关理论书籍中去细嚼慢咽。单从行为本身来看，这是一个比较好的做法，能够更加夯实地建设起我的知识体系。但结合实际情况，在秋招这种时间有限的情况下再回头去抱着书啃无疑是个“错误”的选择。在时间有限的情况下，我更需要的是一个相对来说短期收益明显的方式来快速提升我的纸面实力。我却选择了需要投入大量时间的长效投资。在一次美团的面试后，我认识到了自己的错误。我被面试官从头拷打到尾，把我知识体系刚刚夯实的地基砸了个大洞，往下一探，发现塞满了棉花。我看着面试官从刚开始面试时的兴致勃勃到最后失望离场，心里满是说不上来的滋味。知识无分贵贱，而当时心高气傲的我却不明白这个道理。八股并不是什么上不了台面的东西，相反他是很多工作中积累下来的经验的浓缩。在那场面试后，我也开始找八股，看八股，但是一直找不到面试的感觉了。双非学生收到的寥寥一次面试邀请也大多数停留在了二面阶段。打工/转正小子从22年10月底到23年结束，我也已经在鹅厂干了一年多了。在这里遇到了非常好的导师和同事，也接触到了很多象牙塔里接触不到的事情。在7月初，我被通知有转正的机会。在8月30号迎来了我的转正答辩。当时其实对于转正并不是抱有很大的期待，毕竟同组还有两个研究生。答辩基本上也是当成在腾讯实习的最后一舞来准备了。没想到的是在9月中旬，我收到了我转正通过的信息，接下来便是接意向，签OFFER，谈薪资。在11月，我的鹅厂身份也从实习生变成了毕业生。本来计划的故意弄丢一张工卡挂失留作纪念工卡也可以晚几年再实施了。减肥小子最有成就感的一集在年初时的某一次上称，发现自己喜提82.6公斤并获得了肥仔头衔。高中的我体重一直维持在77公斤上下，看起来已经是有点小胖了。这82.6放在高中是我想都不敢想的数字。痛定思痛后的我开始了我的减肥计划。一开始的计划是通过减少食量轻松减肥，少吃到五六月份发现减得并不是很肥。体重一直卡在80公斤动弹不得。于是6月变开启了我的跑步之旅。[photos]体重,https://cdn.exia.xyz/img/blog/630/image-20240128235734776.png|相关数据,https://cdn.exia.xyz/img/blog/630/image-20240128235832737.png|[/photos]深圳的天气比较热，一开始的时候是隔天跑，每次4km，但是时不时会偷懒不去。但是当我又是某次上称时，看到许久没有变化的体重开始出现轻微下滑时，我开始激发我的小宇宙了。我开始慢慢坚持全勤打卡，每个月尽量跑满15次。天气转凉后开始提升公里数，4km和6km交替打卡。每个月的跑量也在不断提升。5月6月7月8月9月10月11月12月35.54km51.05km72.4km75km65.32km91.56km77.27km87.58km终于，在12月第再次上称时，我的体重已经来到了73.45。[photos]体重,https://cdn.exia.xyz/img/blog/630/image-20240128235917140.png|相关数据,https://cdn.exia.xyz/img/blog/630/image-20240128235934350.png|[/photos]我真的做到了，在一年的时间里减去了将近20斤的体重。而且通过跑步，不只是体重上带来了收益，平时也能明显感觉到人的精神状态好了很多，更加有活力了。想做但是没做的人的精力总是有限的，2023年我也给自己留下了一些未完成的“遗憾”。比如一直想了解的k8s相关技术还没有开始学习、一直想看的一些番也还没开始看、明日方舟的剧情也还卡在12章开头、七娘山和梅沙尖也还没有探索。想继续探索前端工程化相关的技术、想探索自动化追番方案、想换一台更好更大的NAS。想变成更好的自己。2024的Flag2023留下了许多想做的事，在2024年，也要慢慢努力去实现他们。具体来说，可以有下面这些继续完善我的个人项目体系，以用促学保持运动，将体重降低并保持至68kg左右保持输出技术博客，2023年都没怎么写，甚是惭愧大道至简，浓缩成一句话来说就是：保持对生活的热情，期待明天。加油，2024。附录. 今年拍的一些照片[photos]莲花山公园,https://cdn.exia.xyz/img/blog/630/IMG_20230122_171517.jpg|大鹏团建,https://cdn.exia.xyz/img/blog/630/PANO_20230303_132856.jpg|科兴的某天下午,https://cdn.exia.xyz/img/blog/630/IMG_20230714_183824.jpg|[/photos][photos]梧桐山秀桐道,https://cdn.exia.xyz/img/blog/630/IMG_20231011_174418.jpg|梧桐山秀桐道,https://cdn.exia.xyz/img/blog/630/IMG_20231011_173610.jpg|盐田港,https://cdn.exia.xyz/img/blog/630/IMG_20231011_181903.jpg|[/photos][photos]马峦山瀑布,https://cdn.exia.xyz/img/blog/630/IMG_20231102_120216.jpg|学校的晚霞,https://cdn.exia.xyz/img/blog/630/IMG_20231109_172838.jpg|学校的晚霞2,https://cdn.exia.xyz/img/blog/630/IMG_20231109_172934.jpg|[/photos][photos]再访马峦山瀑布,https://cdn.exia.xyz/img/blog/630/IMG_20231112_124626.jpg|三洲田水库,https://cdn.exia.xyz/img/blog/630/IMG_20231112_152332.jpg|三洲田水库2,https://cdn.exia.xyz/img/blog/630/PANO_20231112_152409.jpg|[/photos]
很早之前就看到 React Query 在前端掘金圈子里火了一把，一直想学却因为种种原因没有开始。这一阵子闲下来了一些，快速学习了一下并且在自己的小项目里上手用了一番，体验非常不错。所以在这里以 React Query 为例写一篇 TanStack Query 小小的快速上手指南。React QueryTanStack Query (FKA React Query) is often described as the missing data-fetching library for web applications, but in more technical terms, it makes fetching, caching, synchronizing and updating server state in your web applications a breeze.TanStack Query（前称 React Query，在下文中我们也采用这个称呼）通常被形容为 Web 应用所缺失的数据请求库。更专业的说法是，他让你的 web 应用中的请求、缓存、同步与更新服务端状态更加轻而易举了。从我这段时间的使用体验来看，其中对于请求与缓存的处理可以说是 React Query 给我带来的最显著的提升。以这样一段请求为例👇🏻function getListData() { return axios.get('https://xxxx'); } function List() { // 数据 const [data, setData] = useState([]) // 加载状态 const [loading, setLoading] = useState(false) // ... const queryData = () => { setLoading(true) getListData().then((res) => { // ... setData(res.data.data) }).catch((error) => { // ... }).finally(() => { setLoading(false) }) } useEffect(() => { // 满足某种条件才请求 if (fulfilled) { queryData(); } }, []) // ... // return template; }使用 React Query 管理请求后，我们可以这样写（用过 ahooks 的 useRequest 的小伙伴应该很熟悉这种写法）function List() { // ... const { data, isFetching } = useQuery({ queryFn: getListData, // 查询函数 queryKey: ["list", "query"], // 查询键 enabled: fulfilled, // 是否启用 }); // ... // return template; }在外层，我们需要创建一个 QueryClient 并使用 QueryClientProvider 来提供一个查询的上下文。const queryClient = new QueryClient() function App() { return ( <QueryClientProvider client={queryClient}> <Todos /> </QueryClientProvider> ) }我们把冗长的请求逻辑全部都交由 React Query 来帮我们处理，我们只需要关心请求的状态和数据即可。更详细一些让我们聚焦在例子中 useQuery 这个方法上const { data, isFetching } = useQuery({ queryFn: getListData, // 查询函数 queryKey: ["list", "query"], // 查询键 enabled: fulfilled, // 是否启用 });入参对于入参，这里我们就需要介绍一下 React Query 中最关键的两个参数：queryFn 和 queryKey。查询函数（queryFn）需要是能够返回 Promise 的任意函数。而该 Promise 的返回值将会被填充至 useQuery 返回的 结果数据 data 或是 错误 error 。查询键（queryKey）需要是一个数组，其元素可以是任何可以被序列化的类型。当然， React作为一个开箱即用的库，其本身还包含着一些默认配置。如果你发现 Devtool 的网络 tab 中时不时冒出来一个预期意外的请求。不要着急提Bug，这可能是以下几个默认开启的配置项在“搞鬼”。refetchOnMount：在组件挂载时自动请求refetchOnWindowFocus：在页面被重新focus时自动请求refetchOnReconnect：网络重新连接时自动请求除此之外，还有一种配置会导致重复的请求行为。不过这个需要手动开启，一般不会在开发者不知情的情况下触发请求。refetchInterval：定时请求对于那些默认的配置，你可以通过在 useQuery 的入参中关闭他们，或者在创建 queryClient 的时候就将他们全局关闭。const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnReconnect: false, refetchOnWindowFocus: false, // ... }, }, });返回对于 useQuery 返回的结果，一翻文档发现有25个属性const { data, dataUpdatedAt, error, errorUpdateCount, errorUpdatedAt, failureCount, failureReason, fetchStatus, isError, isFetched, isFetchedAfterMount, isFetching, isInitialLoading, isLoading, isLoadingError, isPaused, isPlaceholderData, isPreviousData, isRefetchError, isRefetching, isStale, isSuccess, refetch, remove, status } = useQuery(options);大多数变量都可以见名知意，但是有一些细心的小伙伴可能发现了，这里的状态有非常多种，看起来貌似非常复杂。别急，让我们细细梳理一番。在 React Query 中，我们有两个独立的状态 fetchStatus 和 status ，分别对应请求本身的状态和结果的状态。什么意思嘞？请求本身的状态 fetchStatus ，也就是指我们的请求函数 queryFn 的状态，它在执行还是在摸鱼？useQuery 给了我们以下几种状态。当 fetchStatus 为 xx 时，fetching：该请求函数正在卖力干活！对应 isFetching。pause：请求函数本应干活，但是因为网络等原因被迫暂停了。对应 isPause。idle：该请求函数正在卖力摸鱼（空闲）！对应 isIdle。而 status 则是对结果 data 的状态描述，我的数据是一个什么样的状态？当 status 为 xx 时，loading：你先别急，还没数据。对应 isLoading。error：你的数据遇到了点错误，具体信息可以看看返回值中的error。对应 isError。success：你的数据安全获取到了，存在 data 里啦。对应 isSuccess。这些状态一般来说就足以支撑我们绝大多数的场景了。关于查询键其实了解了上面的内容，已经可以开始上手开发了。不过对于查询键，咱还是想要多说两句。React Query 作为一个请求管理库，核心就是要将不同的请求统一管理。而对于不同的请求，React Query 通过唯一的 queryKey 来进行标识和分组。举个🌰，对于这些个 queryconst queryA = useQuery(["a1", "b1", "c1"], queryFnA); const queryB = useQuery(["a1", "b1", "c2"], queryFnB); const queryC = useQuery(["a1", "b2", "c1"], queryFnC); const queryD = useQuery(["a2", "b1", "c1"], queryFnD);你可以想象成 React Query 维护了这样的一个集合在未来，我们需要用 React Query 的更高级的功能时，特别是对一些特定的 query 进行操作时，我们只需要通过 queryKey 就可以轻松查找到我们想要的 query 了。例如我需要找出 queryA 和 queryB ，那么我就可以通过 ["a1", "b1"] 来筛选得到想要的结果。所以，在设计或者编写 queryKey 的时候，建议“三思而后行”。最后到这里，这篇小小的指南也要走到尽头了，但 React Query 才刚刚开始，在本文中，我们只讨论了最基础的查询，如果你想实现更多场景，例如分页，无限滚动等。亦或是向服务端发送删除，更新一类的请求（useMutation）。还是需要在文档中继续学习一番。在阅读 React Query 的文档时，我发现了这样一段话Out of the box, TanStack Query is configured with aggressive but sane defaults. Sometimes these defaults can catch new users off guard or make learning/debugging difficult if they are unknown by the user. TanStack 是开箱即用的，使用了一套侵入性但健全的出厂配置。如果不知道这些出厂配置的话，可能会使新用户猝不及防，或是让他们的学习和debug变得困难。想到我很早之前想学习 React Query 的时候，也是受到了某些“坑”的影响，转身干别的事去了（不可取不可取）。我希望在读完这篇小小的指南后，可以帮助你们平缓地度过这一段陡坡~
问题引出考虑如下场景：一个菜单，限高可滚动；其中每一个元素又可以在侧边展开为小菜单，小菜单也同样限高可滚动主流的思路应该是利用大部分组件库都有的 Popup 浮层（我猜的但是如果你的子菜单弹出层是父菜单的元素的子元素，子菜单是靠父菜单的元素 hover 唤出的。比如这样👇🏻 {{ i }} {{ j }} 我的第一直觉告诉我，这不就是一个 overflow 的问题吗？我直接使其在 y 轴上自由滚动，x 轴上也无遮无挡。overflow-y: auto; overflow-x: visible;实际情况是，刷新完页面，清完缓存，再刷再清后，不论我怎么hover，子菜单都弹不出来。最后检查了一下控制台的 computed 属性，发现我的 overflow-x: visible 已经被浏览器偷偷换成了 overflow: auto为什么明明我写的是 visible ，浏览器却解释成 auto 了呢？再尝试将y轴设为 hidden ， x 轴保持 visible 。发现浏览器依然将 x 轴解释成了 auto 。浏览器：“你弹子菜单出来，可以，但是不能滚动；你要滚动，可以，但是不能弹子菜单出来。”问题出现简单翻阅资料后在 sof 发现了想要的回答，有个老哥引用了一段 W3C 规范，原文如下The computed values of ‘overflow-x’ and ‘overflow-y’ are the same as their specified values, except that some combinations with ‘visible’ are not possible: if one is specified as ‘visible’ and the other is ‘scroll’ or ‘auto’, then ‘visible’ is set to ‘auto’. The computed value of ‘overflow’ is equal to the computed value of ‘overflow-x’ if ‘overflow-y’ is the same; otherwise it is the pair of computed values of ‘overflow-x’ and ‘overflow-y’.看到这里顿时茅塞顿开，原来这是 W3C规定的，并不是其他问题导致的无法实现。但是为什么要这样子规定呢？这一层回答下面也有很多感到不解的回复仔细一看，回答是在2011年的，下面几个相关评论也直到17/18年左右，有没有可能已经 W3C 更新了呢？（明显没有，要是更新了不就没有那么多问题了）点开老哥引用的 W3C 链接，发现确实更新了，overflow 属性已经不在那篇文档里了，又稍微找了找，发现了更新的两篇关于 overflow 的草稿https://www.w3.org/TR/2022/WD-css-overflow-3-20221231/https://www.w3.org/TR/2022/WD-css-overflow-4-20221231/在CSS3的草稿中摸了半天，看到了这么一句话The visible/clip values of overflow compute to auto/hidden (respectively) if one of overflow-x or overflow-y is neither visible nor clip.虽然没看到为什么要这样调整，但是W3C这么做一定有他的道理（确信。既然如此，我们只能另辟蹊径了。尝试解决说是另辟蹊径，但是想了半天也没得思路。这个时候我的导师给我支了一招：“你可以试试用 transform 模拟滚动”。这可真是醍醐灌顶的当头一棒。浏览器不给咱们滚，咱们可以用自己滚！监听目标的 WheelEvent 来获得鼠标滚动的 deltaY 的值，通过对应修改容器的 transform ，妙啊！说时迟那时快，一顿操作后简单实现了一下这个想法，同时也发现了一些问题。滚动方向问题：Mac 和 windows 的滚动方向不一致，甚至有些情况下触控板和鼠标的滚动都是不一致的。需要计算滚动的边界问题（避免滚出去滚不回来）子菜单的滚轮滚动也会被监听，导致整个菜单一起滚动层级问题：向上滚动时会遮挡上方同层级的元素...就当我准备把这些难关一一攻克的时候，想起导师说的一句话：“咱们遇到的问题肯定有人都踩过坑，可以去找找有没有现成的库”。关键词：transform + scroll + js搜索结果：ustbhuangyi/better-scroll虽然他是通过鼠标拖拽来实现的滚动，但是配合他的 mouse-wheel 插件也可以实现鼠标滚动。简单上手尝试了一下之后发现确实好用，很多细小问题都解决了。这是初步尝试的效果👇🏻从上面的效果图中可以看出来，虽然我们解决了基础的滚动问题，但是还有两个比较麻烦的问题没有解决子菜单的滚轮滚动也会被监听，导致整个菜单一起滚动层级问题：向上滚动时会遮挡上方同层级的元素子菜单滚动问题研究了一段时间 Better-scroll 文档后，我有了这两个想法利用 Better-scroll 的 nested-scroll 插件利用 Better-scroll 的 preventDefaultException 配置项nested-scroll 插件就是为了嵌套滚动而设计的，应该可以完美解决。而 preventDefaultException 的说明如下我们可以让其在子菜单滚动时让 BS 不拦截滚动，转为原生滚动，岂不美哉。但是现实总是残酷的， nested-scroll 插件是为了拖拽而并非鼠标滚轮的嵌套滚动设计的。preventDefaultException 配置项也不知道为什么不生效...就在要放弃的时候，转念一想，我需要配置 preventDefaultException 的目的就是为了在子菜单的滚动不触发 Better-scroll ，转为原生滚动。那么我可以稍微改造一下 mouse-wheel 插件。增加一个排除DOM的配置项，如果是被排除的DOM发出的产生的 wheelEvent 就直接放行给浏览器进行原生滚动。在 mouse-wheel 插件源码里我找到了这个函数private wheelHandler(e: CompatibleWheelEvent) { // ↓ 在这里增加一个DOM判断！！ if (!this.scroll.enabled) { return } this.beforeHandler(e) // start if (!this.wheelStart) { this.wheelStartHandler(e) this.wheelStart = true } // move const delta = this.getWheelDelta(e) this.wheelMoveHandler(delta) // end this.wheelEndDetector(delta) }如果当前时间的 target 是需要排除的DOM或者其子元素的话就直接 return 掉，不进行拦截。又是一番小改动之后，发现可以完美达到我们需要的效果。层级问题如果是想完美实现 overflow-x: visible + overflow-y: auto 的效果的话，是不应该会出现向上面的 GIF 那样遮挡或者溢出的情况的。这里我们应该怎么解决呢？翻着翻着 MDN 文档，我又找到了个神奇的属性值：overflow: clip。clip 属性与 hidden 很相似，都是将溢出的部分隐藏且禁止滚动。但不同的是 hidden 只是禁止用户滚动（鼠标滚轮、拖拽），并没有限制编程滚动（element.scrollTop），而 clip 直接把编程滚动也给禁止了。但是你猜怎么着，我用的是 transform 来滚动，没想到吧。我给父菜单的容器使用 overflow-y: clip ，然后让父菜单在容器里自由 transform 滚动，我们就得到了下面的效果结语客套话版本：在本篇博客中，我们通过一个简单的小场景引出了 overflow-x: visible + overflow-y: auto 从 W3C 标准来看是不可能的问题。通过对 overflow 与 transform 属性的不断探索，我们也找到了相应的 hack 的方法，在这期间对两个属性以及 Javascript 的事件都有了更加深入的理解。碎碎念版本：其实这是在实习过程中遇到的问题，解决这个问题花了大概两三天时间。在解决这次这个bug的过程中，除了上面客套话版本中说到的对于几个属性的理解更加深入了以外，其实还有一点比较关键——在复杂的业务中定位Bug的能力。在修这个问题的前期，我甚至都找不到是什么原因导致的。这次遇上这个问题也可以说很幸运，学到了很多东西的同时发现了自己的一些薄弱点。路还很长，夯实基础继续前进。
2022年马上就要过去了，相信今年有很多令大家难忘的回忆。我也接着这篇年终总结记录一下我的2022年。我可以把我的2022年按照时间顺序大致分为三个阶段找工作找到工作迷失找工作22年年初，不知道看到了今年互联网就业形势可能会很差的消息。虽然当时春招也只是出现端倪，但是当时大家相对来说没太放在心上，认为只是疫情带来的轻微波动罢了。由于我们学校在大三下需要去企业实习，如果不满足于与学校合作的企业提供的就业岗位，那么就需要自己去找实习。所以这也让没怎么关注就业形势的我开始在这方面上了一些心。我自认为基础很差，所有的功夫只是浮于表面，对自己在找实习这方面并不是很有信心。在看到这则消息后便开始了疯狂的学习。从年初到六七月份，我在JS基础和算法方面花了比较多的功夫。《红宝书》、《你不知道的JS》等更是翻了又翻。算法方面有工作室的Uni学长带路，相对于我原本孱弱的算法能力也是有了长足进步，终于勉强达到了面试者的平均水平（当我冒出来这个想法的时候我就知道可能还没有期间我通过Uni学长的内推尝试面了一下字节飞书。问答环节一切都好，但是无奈手写题的时候还是犯了写算法题时常犯的错误——没读清题就下手。导致这一次面试也是以失败告终。到了8月份，我又参加了学校与腾讯合作的一个暑期项目实践。在这里又接触到了很多优秀的同学和导师。关于这一部分可以看看这篇博客~[art]624[/art]项目实践后，我又去请教了一下在腾讯实习的学长关于找实习方面的问题。在问及该什么时候开始投递简历面试的时候，学长的答复是：“你可以现在就开始了”。于是又在Boss直聘等软件碰壁了两个月之后，终于在腾讯找到了人生第一份实习。找到工作在腾讯工作的日子真的非常充实。每天基本上从9点半开始敲，敲到12点恰个饭睡个觉。下午两点再开始敲，敲到6点再恰个饭，吃饭完回来接着敲。原本快要生锈的脑袋也在这种环境下慢慢转了起来，经常由于脑袋消耗过多能量导致饭点前两个钟就开始肚子咕咕叫。IEG也真的成了挨饿🐔（bushi不过好在在身边都是dalao的情况下，对于各种能力的培养也非常快（自我感觉。经常能get到一些非常好用的技巧或者写法。对于软技能方面又有所提升（除了自我表达能力其他的更多可以看看这里[art]626[/art]迷失在入职了将近一个月后，终于算是找到了实习的节奏。平时自己自由支配的时间也慢慢多了起来，但是人的惰性也如期而至。从之前的学习经验来说，最重要的还是学习中沉淀的过程。在前一个月，由于还没适应工作节奏，导致了很多无故加班，回到家洗洗差不多就可以睡了，没什么时间沉淀自己。除此之外，我也不想这一次实习只是一次实习，谁不想留在大厂上班呢。但是大厂的转正留用考核又哪有那么简单，加之我的潜在竞争者基本上都是名校研究生，感觉又是难上加难。在前方有着上面说的两堵大墙的情况下，我也第一次拿到了自己的工资。这笔工资对于我来说，真的是打开了很多以前没有打开过的大门。对于一个算是刚刚步入社会的学生仔来说，真的像是打开了潘多拉的盒子。我突然发现有趣的东西有很多，这里可以试试，那里可以玩玩。在这一段时间里，我曾经拥有很多短暂的爱好。实际上却都是泡沫，除了浪费掉我的时间意外并没有什么实质性的价值。这一段时间也许可以用黑暗来形容。我自我安慰自己说我一个21世纪的大学生，花点这些钱不是也很正常吗？可能正常，但是真的合适吗？总结总结来说我个人认为2022年我最大收获有两点：一是心性的转变，从学生变成了打工人。很多时候不会再像学生那样去思考问题了；二是完成了一段相对圆满的追梦之旅，找到了一份大厂的实习。也可以说是自己的能力得到了一定的肯定吧。同时也在很多方面暴露出了问题，希望能2023年找到解决方案。拜托了，2023年的我。加油啊，2023年的我。
今天是入职腾讯后的第18天，上周五的时候终于结束了第一个需求，也终于有时间来总结一下过去这一两个月从面试到入职到现在的一些经历。敲一敲鹅厂的大门🚪作为应用型技术大学，深技大有个特色：每个同学大三下半学期都需要去到企业实习。我自然也逃不了大三下的社畜体验券。但是自从2022年开始，互联网的寒气似乎在慢慢扩散开来，身边几个原本一起学习前端的同学也润考研了，加上一些在大厂的学长都说不容易，我也渐渐开始紧张起来。虽然说不是要第一份工作就一定要找大厂，但毕竟求其上者得其中，求其中者得其下，还是希望能对自己要求高一点，多尝试，多感受。给自己订的计划是先从中小厂开始练手，锻炼一下自己的面试能力，再去以更好的状态尝试大厂。但是现实并没有想象的那么美好，在Boss直聘等平台沟通了一些比较心动的中小厂，得到的都统一回复：暂时不招24届的哦。四处碰壁后也有些疲惫了，开始佛系起来。直到9月初在CSIG的学长找到我，问我想不想去试一下。一拍大腿，冲了！从9.22面到10.8，最后10.17收到OFFER，约了在10.27入职。鹅厂的生活从需求开始📔在正式入职之前，从隔壁组的学长和我们组组长的言语里都能感受到那边是真的着急用人，自己心里也做好了到岗立马干活的准备。但是没想到的是，我们组似乎比我预想的还要忙。入职第一天上午，办公用的笔记本还没有配到，导师已经跟我介绍起了需求和需要用到的相关技术栈。下午电脑送到，系统配好，晚上就开始跑起项目看代码了。我的腾讯社畜生活也就此拉开序幕。工作氛围真不错👍好的工作氛围我认为主要体现在两个方面：轻松的人际关系和高效的工作模式。在这两周，我感受到了很多在大厂工作的魅力。首先，同事间的关系非常轻松和睦，组长和组员们都是打成一团。大家对我和我同学两位新人也非常包容，会对我们进行一些指导。其次，工作的时间也非常弹性，大家可以非常自由地调整的工作时间。平时大家在工位上的时候，都非常专注，休息的时候，大家也很放松。最后一点是一个相对具体一点的细节，但是对我来说是感受比较深的。同事们在沟通中的效率都很高，很多时候提出问题或者方案，他们都可以非常清晰明确地进行描述，降低了很多沟通成本。另一个方面，工作中难免有讨论，有激烈的讨论，但是同事们大多都是第一性原理的有力执行者。不论讨论的多激烈，仔细听就会发现，其实都是在非常客观地去表达自己的观点，阐述自己的想法，而不是伴随着一些情绪。综合这几点，我感觉这一次在腾讯的实习我一定会受益良多。我的感受👦我是慢热型选手，接触新东西的时候经常需要花一些时间去消化整理（脑内构建索引）。一入职就开始写需求导致了很多问题。为了完成任务，我几乎没有什么时间去了解公司的环境，公司的各种系统用起来也磕磕绊绊。而且项目非常庞大，我花了非常多时间去捋清代码。更重要的是我很少有时间去沉淀一些知识和经验。当然，一上来就真刀真枪也是有好处的。需求ddl的压力显著加速了我这慢热型选手蜗牛般的适应速度。我明显感觉到我对项目代码的熟悉速度是最快的一次。而且工作中难免会与很多同事对接、打交道，照往常我可能要慢慢地去融入开发团队。但是这一次，从导师带着我去和别人对接到自己有问题去找相关同事对齐，无形中我也更加迅速地融入了团队。说到底还是挣脱舒适圈过程中的阵痛期吧。对自己的期待💪两周过后，真的能感受到这次实习是肯定不虚此行。那就在这里给自己定下一些小目标吧学会高效沟通学会快速适应在自己的专业领域能够有所成长这几个目标说大不大说小不小，但是真实希望自己可以在明年结束实习之前能够在这些方面有所提升。加油！
在上一篇博客中，我们了解了浏览器如何将开发者编写的 HTML、CSS 和 Javascript 转化成最终显示在用户屏幕上的画面。在这一过程中，我们认识到了光栅化以及合成器线程，而今天的这篇博客也与其有着密不可分的关联。输入事件（input events）从用户的视角来看，可能输入时间就是那些点击、输入或者拖动。但是从浏览器的角度来看，用户的一切动作都可以看作是输入事件，例如鼠标滚轮滚动和鼠标悬停。浏览器进程是首先捕捉到用户的输入事件的，但是浏览器只是知道用户进行了这样的一个操作，如何对用户的操作进行反应还是会交由渲染器进程处理。浏览器进程将用户的输入事件经过合成线程传递给渲染器进程，包括事件的类型（如 touchstart）和坐标，由渲染器主线程和合成线程相互配合完成对事件的响应。那么问题来了，渲染器进程是如何通过这个坐标和事件类型来判断用户到底在哪个元素交互，想要触发什么动作呢？处理输入事件细心的朋友可能发现了，在上一篇博客里面最后其实也涉及到了一些简单的输入书简处理的问题。用户滚动鼠标滚轮，合成器线程生成一个个合成帧最终渲染到屏幕上，这就是最基础的输入事件处理。在上篇博客的简单例子中，没有什么对于滚动的监听函数，合成器线程可以独立地完成这一次事件的处理。当页面变得复杂，合成器线程和渲染器主线程的配合才得以体现。两者配合的大致过程如下：浏览器进程接收到输入事件，将其坐标和事件类型传递给合成器线程后，合成器线程判断输入事件是否会触发监听回调，如果会触发就通知渲染器主线程去执行相应的 Js 函数，等到Js执行完后再去生成合成帧；如果不会触发，那么就和上面的滚动一样了，合成器不需要通知主线程，自己就可以直接渲染出一个合成帧丢给GPU。聪明的合成器在这个过程中，合成器如何快速判断点击到的位置是否包含有监听器呢？首先排除每一次都从DOM树上找的方案。chrome的开发者们找出了一个非常高效的方法，为每一次合成出来的页面打上记号，通过记号来快速判断。具体来说，合成器在合成当前的页面后，会对每一个包含挂载有事件监听器的元素的区域打上一个 non-fast scrollable region 的记号。这是直接对与区域的标记，如果点击的位置不属于被标记的范围，那合成器直接自己去合成新合成帧就可以了。如果在标记的区域内，那就需要再等等 Js 的执行了。辛苦的主线程假如点击到了带有事件监听器的区域，合成线程将事件传递给了主线程。主线程在拜托 Js 执行回调函数之前，还需要先找到用户到底是在和那一个元素交互。主线程可不像合成器线程一样能走走捷径，为了准确找到目标元素，它需要先进行一次 hit test。所谓的 hit test 其实就是在渲染过程中生成的 paint records 里去找到该事件点击的坐标上的绘制顺序，最后找到被交互的目标元素。最终，找到了目标元素的渲染器主线程将各种信息（坐标、目标元素、事件类型）等打包成我们熟知的 event 对象传递给开发者定义的回调函数去执行。如果执行的过程中改变了DOM，合成器继续等，等到重新渲染出新的 Layout tree 和 paint records 来合成新的合成帧。如果没有改变DOM，那就直接渲染就好啦。事件合成在之前的博客中，我们讨论过为了让用户有更加流畅的观感，以普通的显示为例，浏览器每秒交错刷新60次。但是对于输入事件来说，一般的触屏设备每秒可以分发60～120次的触摸事件，一般的鼠标可以分发100次左右的事件。可以看得出来，用户输入的事件的频率是远超我们的屏幕刷新次数的。考虑一个类似 touchmove 的持续输入事件，浏览器如果不经任何处理，每秒可以将差不多120个事件塞到渲染器主线程去进行一次又一次的 hit test，这远远超出了屏幕刷新的速度。想到这里，可能有些经验老道的朋友就想到了，我们可以利用节流来限制呀。就像例如点赞等一些经典场景，如果前端一监听到有人按下点赞按钮就向后端发送请求，如果碰上一群手速怪人，服务器可不好受。前端工程师为了防止服务器被这些人 gank，就心生一计，把一段时间内的点赞合成一个请求发出去，就算用户点得再快，也不过是按照我们预设的频率请求。浏览器工程师直呼“英雄所见略同”。为了让渲染器进程不会被用户随便轻轻一拉就冲垮，他们将一段连续的事件合并到一起发送给渲染器进程。当我们不太关注用户手指滑动的路径的时候，其实在一段时间内的滑动最主要位置的也就是起点和终点的坐标。通过合并这一操作，渲染器进程处理用户输入就变得相对游刃有余了。在优化后，我们可以发现合并后的时间派发给渲染器进程的时间点都在下一次 requestAnimationFrame 之前，确保能在之后执行的 Js 中获取到。一般来说只有那些连续的事件会被合并，例如 wheel, mousewheel, mousemove, pointermove, touchmove 。而像 keydown, keyup, mouseup, mousedown, touchstart 这种事件则会被立即派发到渲染器进程。可是问题也来了，如果我们确实很关心用户滑动的路径呢，例如我想实现一个在线画板之类的东西。按照上面的逻辑，用户的画都变成一个个线段了。这可咋整呢？浏览器工程师也想到了这一点，并为开发者们开放了一个接口 getCoalescedEvents 。通过这个方法就可以获取到被合并的事件了。window.addEventListener('pointermove', event => { const events = event.getCoalescedEvents(); for (let event of events) { const x = event.pageX; const y = event.pageY; // draw a line using x and y coordinates. } });性能优化？在我们知道了浏览器是如何处理用户的输入事件之后，我们也许可以去思考一下如何让我们的代码能更加充分地利用浏览器的一些特性。或者至少不会拖慢浏览器应有的速度。事件委托事件委托是现代 Js 编程中常见的处理事件的方式。考虑一个列表 ，其中有很多的列表项 。你想为其中每一个列表项都添加一个点击事件，如果为每一个 都加上一个 onclick 无疑会对浏览器造成不小的负担。而如果按照事件委托的方式去处理我们的需求，利用事件冒泡的机制，我们可以将 onclick 挂在 上，子元素的点击事件都会被父元素的这个 onclick 捕捉到，在父元素的回调函数内加上一个简单的判断，就可以实现相同的效果了。从开发者的角度来看，这无疑非常方便，既减轻了工作量，又减轻了浏览器的工作负担，岂不美哉。确实，在上面的例子来看，这样子优化确实非常有效。但是考虑下面的场景。document.body.addEventListener('touchstart', event => { if (event.target === area) { event.preventDefault(); } });不知道出于什么样的精神状态，一位开发者给 body 挂上了事件监听器。这个时候合成器线程傻了，好家伙，但凡这页面有关于 touchstart 的一点风吹草动都得去问问主线程。主线程也傻了，每次都去找一遍目标元素，交给 Js 引擎去执行回调函数。回调函数一看，😯，不是我想要的元素，大家就当无事发生。合成器听了沉默，主线程听了流泪。在这种情况下，合成器线程的优化一点效果没有不说，甚至完全就是负作用。幸好要解决这个问题还算比较简单，只需要在挂载监听器的时候加上 passive: true 参数即可。document.body.addEventListener('touchstart', event => { if (event.target === area) { event.preventDefault() } }, {passive: true});这个参数用于提示浏览器，我虽然这么监听，但是可能90%的触碰都是无效的，你合成器进程不用管我，自己合成就好。This hints to the browser that you still want to listen to the event in the main thread, but compositor can go ahead and composite new frame as well.试试 Lighthouse在读完了现代浏览器系列的四篇博客之后，你应该已经对浏览器的运行原理有了一个大体的了解。对什么东西会影响到网页的性能可能也有了自己的认识。如果想要优化自己的页面，lighthouse 可以给你提出一些针对性的建议，快打开开发者工具去试试看吧！结语《深入理解现代浏览器》系列的读书笔记到这里就结束了。这几篇博客给我带来的知识感觉超越了其本身的内容，更重要的是它将我之前对于前端一些知识点的错误理解以及一些疑惑都进行了一个改正与点拨，让我真真切切地有了一种豁然开朗的感觉。果然学习还是要深入原理才可以呀。