普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月9日掘金 前端

前端基础数据中心:从混乱到统一的架构演进

2025年12月9日 17:41
一、问题是怎么来的 做过 B 端系统的同学应该都有体会——基础数据无处不在。港口、船舶、航线、货币、字典……这些数据在几乎每个页面都会用到,要么是下拉选择,要么是代码翻译,要么是表格筛选。 我们项目一

2025 复盘 | 穿越AI焦虑周期,进化为 "AI全栈"

作者 coder_pig
2025年12月9日 17:36

1. 今年一定

好几年没写「年终总结」,翻了下「掘金」,上一篇还停留在「2021年」,倒不是这几年没活过,也不是不想写,只是每次都止步于「拖延」。每到年底,脑子里就会蹦出的各种想法,被我零散地记录在 "大纲笔记" 中:

写这玩意挺废「时间」和「心力」,所以总想着 "找个周末,花一大块时间,好好梳理一下",结果都是一拖再拖:拖到元旦,拖到春节,拖到元宵。

然后,转念一想:"这都明年了,还写个🐣毛啊?算了,算了,明年一定!"。2022、2023、2024 就在这样的 "明年一定" 中溜走了...


人到中年,主观感觉「时间」过得越来越快,「记性」 也大不如前,很多当时觉得 "刻骨铭心" 的瞬间 (如:结婚、当爹),如今回忆起,就剩下一个 "模糊的轮廓"。不写的话,又是「 🐟」一年,趁着 2025 年还没过完,很多感觉还是热乎的,赶紧动笔,今年一定!!!

2. 如此生活30年,直到大厦崩塌

2025 年,对我冲击最大的当属 "AI",大到让我不得不重新审视「自己的工作和价值」。

我接触 AI 并不算晚,2024 年那会跟风玩了下 ChatGPT,后面在 GitHub Copilot (便宜盗版,30¥/月) 的协助下,快速交付了一个爬虫单子。尝到 "提效甜头" 的我,咬咬牙上了 年付正版 (100刀/年) 的车。

当时 AI 在我的眼里,只是个 "比较聪明的代码补全工具",可以帮我少些几行代码,仅此而已,因为 逻辑还得我来把控

但到了 2025 年,事情却变得有点不一样了:

AI能读懂/分析整个项目、重构屎山代码、把一个模糊的业务需求实现得有模有样。

当然,最让我 "破防" 的还是它的 "解BUG" 能力:

按照以往的习惯,我得去 Google、翻 Stack Overflow、看源码,少说得折腾半天。现在,把 报错日志相关代码 丢给它,通常几秒就能:指出问题所在给出解决方案,甚至可以 帮我改好并测试通过

积累多年,一度 "引以为傲" 的「编程经验」(对API的熟练程度、快速定位BUG的直觉、配置环境的熟练度等) 在 AI 的面前,变得 "不堪一击"。渐渐地,我的「工作流程」也发生了改变,"亲手" 敲下的代码越来越少,取而代之的是一套 "机械化" 的 "肌肉记忆":

  • 写注释等待AI补全按Tab:哪怕脑子里知道下一行该写什么,手指也会下意识停顿,等待那行灰色的建议浮现,然后无脑按下 Tab。
  • 提需求生成代码Accept:把业务逻辑描述一遍,丢给 AI,都不太细看生成的具体实现,直接点击 Accept All,主打一个 "能跑就行"。
  • 运行报错复制日志丢给 AI:遇到 Bug,第一反应不再是去分析堆栈信息 (Stack Trace),而是CV日志到对话框,问它:"解决报错:xxx"。
  • 效果不对截图丢给 AI:连描述问题的精力都省了,直接截图往对话框里一扔,附上一句 "改成这样"。

代码跑通了,效率提高了,却带来了精神上的「空虚」,我似乎再也感受不到当初那种「编程的快乐」:

  • 为了解决某个问题,苦思冥想,抽丝剥茧,最后成功 "破案" 时 "多巴胺疯狂分泌" 的 "快感"。
  • 查各种资料、反复推敲、验证,最终设计出一个自己觉得 "牛逼哄哄" 代码架构时的 "成就感"。

同时,也陷入了一种深深的「自我怀疑与迷茫」:

  • 越来越搞不清楚自己的「定位」(存在价值),上面那套 "连招" 找个实习生培训两天也能干。我曾赖以为生的那些 "技能",正变得廉价、可替代、甚至有点多余 ...
  • 找不到方向,以前「程序员成长路径」很清晰:学语言 → 学框架 → 学架构 → 学系统设计 → 刷算法 → 搞源码 ... 只要你一步步往上爬,爬到 "山顶" 就能成为 "大牛"。而如今却好像 "失效" 了...
  • 可控感被剥夺,程序员是典型的「内控型人格」—— 相信通过逻辑和细节掌控能预测一切。而但 AI 的「黑箱特性」带来了「工具不可控」,无法完全准确预测AI输出,调试从 "追踪逻辑" 变为 "试探模型"。

3. 调整对待AI的心态

3.1. 从 "焦虑" 到 "接纳"

我深知「焦虑」无用,于是开始探寻「破局之道」,反复阅读大量资料后发现,几乎所有人都在让你「拥抱 AI」,但具体怎么拥抱法?没人说,或者说得含糊不清,有些甚至还想割我 "韭菜" 🤡 ?屏蔽这些噪音,冷静下来复盘,拨开情绪迷雾,透过现象看本质。

首先,坦诚地「接纳」肯定是没错的,历史的车轮从不因个人的意志而停止转动,当 第一次工业革命的蒸汽机 轰鸣作响时,那些坚守手工工场的匠人们,也经历着相同的困境。精细手艺 在不知疲倦、效率千倍的 机械化工厂 面前显得苍白而无力。大机器生产取代手工劳动,不是一种选择,而是一种必然的 "降维打击"。

现在,我们同样站在了 "生产力变革" 的周期节点上, "效率至上" 的底层逻辑从未改变。是选择成为被时代甩下车的 "旧时代纺织工"?还是进化为驾驭机器的 "新时代工程师"?回归「第一性原理」,剥开 "智能" 的外衣,想想 "AI 的本质是什么?" —— 「干活的工具

所以,面对 AI,我们要做的事情就是琢磨 "如何用好这个工具? ",即:详细阅读使用说明后,在合适的场景,用合适的方式,解决合适的问题。

3.2. AI 有什么用?—— 能力放大 + 自学利器

3.2.1. 能力放大器

🐶 经常在 自媒体平台 刷到 "普通人学AI后致富/逆袭" 的 叙事,看到这些 "逆天标题" 没把我笑死:

多的不说,记住这段话就对了:

变现的核心能力从来不是使用工具,而是商业认知、市场洞察、营销推广、客户服务。AI 只是一个环节,不要高估了工具的作用,而低估了 商业常识的重要性,也不要低估了背后的 隐性成本和巨大的工作量

这些 "AI变现教程" 的 最大问题

让你把AI当成一个 独立的、全新的、需要从零开始的 "行业" 去卷。

对于 99% 的普通人而言,把AI看作 "能力放大器" 会更靠谱一点,即:

思考如何利用AI,帮我把我已有的技能/兴趣做得更好?

比如:

  • 用 AI 减少重复劳动,提高工作效率和质量,把时间花在更有创造力的事情。
  • AI 负责广度,你负责深度,在你热爱的小众领域里用AI武装自己,做到 "人无我有,人有我精"。

今年「Vibe Coding (氛围编程) 」很火:

用自然语言描述想要的效果,AI帮你写代码,你只负责验收结果和提修改意见,不用管具体代码怎么实现的。

编程门槛大大降低,普通人 只要能把 创意和感觉 翻译成需求,就能借助 AI 将其快速具象化为 可运行的产品

但你会发现,绝大多数生成的作品都是 "一次性原型或玩具":灵光一现即可实现,却缺乏持续迭代、架构设计与用户验证,因此难以具备商业价值、也难形成可持续的产品形态。

真正能够利用 Vibe Coding 实现变现的,往往是具备一定 编程经验产品思维 的 "专业人士"。他们不仅能用 AI 快速实现灵感,还能对作品进行持续优化、迭代和工程化打磨,从而将 "灵感原型" 进化为 "可用产品"。

再说一个自己观察到的例子,前阵子 OpenAI 发布了用于生成短视频的「Sora2」,B站 很快涌现了一堆 AI 生成的 "赛博科比" 恶搞视频。

看到一个播放量破百万的作品有点意思,点进 UP 主的主页想看下其它作品,结果发现他并不是突然爆火的 "新人",人家已经默默做了好几年视频,只是过去的播放量惨淡 (几十几百)。但他却一直坚持创作,尝试不同的方向,能清晰地看到他的剪辑、叙事和整体制作水平在一点点提高。

AI 不会让没有积累的人"平地起飞",但有可能让有准备的人"一飞冲天"。—《抠腚男孩》

3.2.2. 自学利器

看到这里,可能有人会问:

"那普通人怎么办?我没啥专业技能,也没有长期积累啊? "

简单,那就 "" 啊!!!以前学习的最大限制是什么?

没人教、教不好、学不动、坚持难

而现在,你有了一个「全知全能、知无不言、24小时为你服务的免费老师

  • 不会写代码?手把手教你,从逻辑到示例一步步拆开。
  • 想转行?给你路径、资源、练习清单、复盘建议。
  • 想跨领域?帮你建立知识框架,把陌生领域最难啃的部分变简单。
  • 遇到瓶颈?像一对一导师一样不断提问、引导、纠偏。

当然,想要这台 "自学利器" 高效运转起来,实现 快速学习/试错/跨域 还需要掌握一些 方法论

详细解读可以参加我之前写的《如何借助AI,高效学习,实现快速"跨域"》

3.2.3. 不要神化 AI

🐶 2333,经常刷到 "xx公司发布新的 xx 模型/AI产品颠覆行业,xx师要失业了" 的标题,但事实真的如此吗?最近 Google 家的 Nano Banana Pro 🍌很火,号称当前 "最强AI生图" 模型,亲身体验下确实强 (本文大部分配图就是它出的),天天在群里吹爆。

某天晚上,有 "多年专业设计经验" 的老婆收到一个改图需求 (抠素材,按要求调整海报):

😄 看着简单,感觉 🍌 就能做,于是我提出和老婆 PK 下,她用 PS 改,我用 🍌 嘴遁修图,看谁出的图又快又好。结果:她10分钟不到就改完,而我跟 🍌 Battle了半个小时没搞好,最终的效果图 (左边她的,右边我的):

🤡 "甲方" 的评价 (破大防了😭):

观察仔细的读者可能会问:"你是不是漏了一个车🚗啊?",憋说了,这破车把我调麻了...

那一刻,我深刻体会到了什么叫 "不要拿你的兴趣爱好,去挑战别人的饭碗",真的是 "降维打击" 啊!

AI 确实拉低了创作的门槛,但目前还处于生成 80分 内容的阶段 (效率),最后的 10-20 分 (细节、审美、情感) 才是价值的核心。——《抠腚男孩》

后面复盘,老婆看了下我的 Prompt,说我的流程有点问题,应该让 AI 先把素材全抠出来先,再慢慢组合。后面试了下,效果确实有好一些。不过,不得不说,AI自动抠图 这块确实可以:

🤣 老婆在日常设计时也会用 AI 来偷懒,比如:生成配图、提高清晰度、扩图等。

3.3. AI 是什么? —— 概率预测机器

现阶段谈论 AI,其实都是在谈论 大模型 (LLM) —— 一个极其复杂的 "概率预测机器"。

通过学习海量数据的 "统计规律",逐步逼近这些数据背后的 "概率分布",从而能够在给定 "上下文" 时预测最合理的下一步输出。

不同类型产物的生成原理图解 (看不懂没关系,简单了解下即可):

文本

② 图片 (扩散模型 & 自回归模型)

③ 音频 (自回归模型 & Codec + Token 预测 )

④ 视频 (扩散式 & 自回归/时空Token)

3.4. AI 的能力边界 —— 优/劣势

LLM 擅长发现 "相关性",但难以进行真正的 "因果推理",它只是在 "模仿智能",而非 "真正地理解意图,拥有意识"。 —— 《抠腚男孩

弄清楚 AI 的本质是 "概率预测机器" 后,接着从 "代码生成" 的角度梳理下它的 "优势 & 劣势":

了解完 AI优/劣势 后,接着就可以推演「人 & AI」 的 高效协作方式

一句话概括

AI 负责 "生产力" (重复、繁琐、高上下文、高整合的工作),人负责 "方向与边界" (判断、创造、决策、理解组织与业务)。

4. 必备技能 —— Prompt

一般译作 "提示词" 或 "描述词",个人认为后者更加贴切,即:描述问题/需求的 "词句组合" 。「Prompt Engineering-提示词工程」是所有人都必须掌握的 "使用AI的核心技能"。

4.1. 把话说清楚

🐶 别被几个英文单词吓倒,现在的 AI 比几年前聪明多了,普通人 只要能:

把诉求讲得清晰、完整、有逻辑,就能解决绝大多数问题

示例:

  • ❌ 混乱说法:帮我计划个周末玩的地方。
  • ✅ 有条理说法:周末想带5岁孩子一日游,2大1小,预算500以内,北京,不想跑太远,能放电、有吃饭的地方、避开暴晒,地铁可达最好。

AI输出结果 (前者输出不同城市的游玩方案,后者输出了具体的行程方案):

4.2. 套框架

再往上走,就是了解一些经典的 "Prompt框架",然后再提问时套用,以提高 AI 输出的稳定性、准确性和质量。所谓的 "框架",其实就是 "结构化模板",规定问题中包含哪些 "要素",比如最经典的「CTRF」框架:

套框架示例 (填空题~):

常见的框架还有 RTFCOSTARSPARCOTAPE 等等,适用于不同的场景。杰哥整合了自己知道的所有框架精华和高级技巧,弄了通用的「Prompt 最佳实践清单

无脑套就是了,助记口诀

也可以用故事流程来串联助记,读者可自行发挥,顺序无需固定:

让一位说书人 (角色) ,用生动的语气 (风格语气) ,给孩子们 (受众) 讲个故事 (指令) 。故事的开头 (上下文) 是...,结局 (目标) 要感人。故事的结构 (格式) 要像这样 (示例) ,但不要 (约束) 出现暴力情节。请先构思情节 (逐步思考) ,写完后再想想怎么能更精彩 (反思) 。

😄 懒得记的话,可以用我之前搭的小工具 →「CP AI Prompt助手」

配下 DeepSeekKey,复制粘贴你写的 简单Prompt,它会基于上面的十个维度对提示词进行优化:

4.3. 写出牛逼的Prompt

明白了怎么 "套框架" 写 "结构化的Prompt",但你可能还是会感到疑惑:

用的同样的AI,为什么别人的生成效果就是比我好?

尤其在 AI 生图 领域,看大佬分享的 Prompt,里面一堆看不懂的专业参数:

环境、构图、光影、景深、镜头、光圈、色调、氛围、胶片颗粒、对比度、主体增强、氛围灯...

能写出这么 专业的Prompt,是因为他们有 "相关领域的行业经验" 吗?

答:有加成,但不全是。高手的核心技能不是 "记这些专业知识",而是:知道如何指使 AI 给自己提供专业知识、框架、术语,然后再反向用这些框架让 AI 编写和优化 Prompt。

😄 其实思路很简单,拆解下这套方法论:

维度词术语/词库通用模板填空得第一版PromptAI专家视角优化迭代优化沉淀

详细玩法可以看下图:

4.4. Prompt 逆向

Prompt 逆向工程RPE,Reverse Prompt Engineering),就是:从 "输出" 反推 "是什么Prompt" 生成了它。一般用于:学习优秀案例调试和诊断问题构建Prompt库和模板企业质量控制安全审计 (防御Prompt注入攻击)。

4.4.1. 简单版

普通人 用这个套路就够了,选个聪明点的模型 (如:GPT5Gemini 3 Pro),粘贴图片,写 Prompt 让它反推:

差得有点远,描述「不满意的点」,让AI继续优化Prompt:

接着用优化后的 Prompt 来生成,可以看到效果差不多了,接着让 AI 提取一个「通用的Prompt

拿 AI 生成的 Prompt 生图,看效果,描述问题,循环反复,直到稳定生成自己想要的效果~

4.4.2. 专业版

🐶 其实也差不多,只是流程比较 "标准化",经常搞还能自己搭个 "工作流",适合专业选手,思路:

快速拆解推断 Prompt提取要素重建 Prompt优化迭代模板化沉淀

详细图解:

上面是通用的,还有几个 额外功能 的玩法也罗列下:

5. 锦上添花——懂点AI常识

🐶 懂点AI常识,能让你更 有的放矢用好AI (装逼),比如:连 Token 都不知道的话,就有点贻笑大方了。这里只是简单罗列下相关名词,不用死记,有个大概印象即可,不影响你使用AI,跳过也没关系。😄 详细讲解,建议直接复制名词问题AI,也可移步至《AI-概念名词 & LLM-模型微调》自行查阅~

5.1. AI (人工智能) 基础概念

5.2. NLP (自然语言处理)

5.3. Transformer 架构 (大型模型基础)

5.4. 语言模型基础 (Language Models)

5.5. LLM 核心概念 (Large Language Models)

5.6. 数据与训练流程 (Training & Fine-tuning)

5.7. 推理阶段 (Inference)

5.8. RAG (检索增强生成)

5.9. 多模态 AI

5.10. AIGC (生成式内容)

5.11. 模型压缩、部署与加速 (LLMOps)

5.12. Agent (自主智能体)

5.13. AGI (通往通用智能)

6. AI 编程领域专精

😄 最后,聊聊 AI 编程 领域的一些心得~

6.1. 前置知识

6.1.1. 编程模型

AI 代码写得好不好,主要看 "模型" 的 "编程能力",评估 "模型优劣" 的几个 "常见维度":

LLM 的能力很难用一句话概括,所以厂商们每次发新模型都会用一堆 Benchmark 来证明 (🐶不服,跑个分?)

推理与数学能力 (Reasoning)

"智能的核心指标",高分意味着能够做更复杂任务 (如:工程规划、Agent等),常见基准:GSM8K-小学奥数式数学题、MATH-高难度数学、AIME/AMC-奥林匹克数学、GPQA-博士级科学问答、BigBench Hard (BBH)-推理难题集合 等。

语言理解与知识能力 (Language / Knowledge)

"通用模型 IQ 测试",常见基准:MMLU-大学生多学科理解测试、MMLU-Pro - 更难版本、ARC / HellaSwag - 常识推理、OpenBookQA/TriviaQA - 事实/知识问答 等。

③ ✨编程能力 (Coding)

"商业价值极高的应用点",常见基准:HumanEval - 函数级别代码生成、MBPP / MBPP+ 简单编程题、SWE-Bench / SWE-Bench Verified✨:真实 GitHub issue + 多文件工程 (最接近真实开发场景,近两年厂商都在比这个)、Codeforces-算法比赛、CRUXEval / RepoBench-项目级分析 等。

多模态能力 (Multimodal)

"下一代 AI 产品的必争之地" (做 AI 助手、看图、自动化办公等),常见基准:MathVista:带图的数学推理、ChartQA / DocVQA:文档理解、TextCaps / ImageNet:视觉场景理解、VideoMME:视频理解、V-Bench / VQAv2:视觉问答 等。

安全性 (Safety / Robustness)

企业用户很看重 "安全合规",常见基准:Harmlessness / TruthfulnessAdvBench:对抗攻击、Red Team 红队测试Over-Refusal 测试(不会乱拒绝)、Speculative Safety(推测生成的风险)等。

速度/延迟/吞吐 (Performance Metrics)

"决定实际用户体验",常见指标:Tokens per second (推理速度)、First Token Latency (首字延迟)、Throughput QPS (每秒处理请求数)、Context processing speed (长文档处理速度)。


有时还会发布一些 "技术参数":

  • 模型规模:模型的参数量大小,影响推理与表达上限,规模越大,能力越强,但成本、延迟和部署难度也越高。如:70B = 70 billion = 700亿参数。
  • 训练数据规模:模型预训练时学习的 token 总量,代表其知识 "阅历"。数据越多通常知识覆盖越广,但质量、去重和清洗策略比单纯堆量更关键,高质量数据才能让模型表现更稳。如:15T = 15 trillion = 1.5万亿个token。
  • 上下文窗口:模型单次可接收并 "记住" 的 "输入长度上限",决定你能塞多少代码、文档和对话历史;窗口越大越适合做整仓分析、长文档问答、复杂任务,但会牺牲成本和延迟,且需要额外机制确保在超长上下文中仍能抓住重点。
  • 推理深度:模型答题时的 "思考力度",深推理模式更准确、适合复杂问题,但会更慢、更贵,适合关键任务而非高频交互。
  • 价格:按 token 收费,区分输入价、输出价与最小计费单位;部分模型提供 "缓存命中 (cache hit) ",对重复提示只按更低费率计费,大幅降低长上下文与多轮调用成本。价格决定模型可否大规模、频繁和低成本使用。
  • 延迟标准:包含首 token 延迟 (FTL) 与 生成速度 (token/s),分别决定 "多久开始回应" 和 "内容生成有多快";低延迟让补全、对话、Agent 流程更流畅,而高延迟会严重影响开发体验与实时性,是工程中比 "更聪明一点" 更重要的性能指标。
  • 模型行为控制能力:通过 Temperature、Top-p、System Prompt、工具权限等机制控制模型的随机性、稳定性与执行边界;行为越可控,越能确保输出一致、不跑偏,并安全地接入工具链或生产系统,是把模型从 demo 提升到可上线能力的关键参数。

🤡 个人 "主观" 认为的 "编程模型" 能力排名:

😊 一句话概括我的 "选模型策略":

选好的模型事半功倍!工程大活Claude精细小活 (如改BUG) 用 GPT,写前端页面用 Gemini

🐶 问:这些都是国外的模型啊,怎么才能用上?A社还锁区,经常封号?而且价格好贵啊?

答:😏 这个问题充钱可以解决.jpg,多逛下海 (xian) 鲜 (yu) 市场,国人的 "薅羊毛" 能力不是盖的,各种 "镜像站、第三方中转" 。氪金的时候注意找有 "售后" 的,随用随充,买 "短期 (如月付) ",不要买 "长期 (如年付)",这种看 LLM官方 政策的,一封就直接G了,说不定就 "卷款跑路"~

6.1.2. AI 编程工具的四种形态

😄 一句话归纳:

普通开发者 & Vibe Coding用户AI IDE/插件 居多,DevOps/后端工程师CLI团队/企业系统云端AgentAI 应用开发者AI SDK 构建构建 AI 产品与 Agent 系统。

接着说下 "AI编程" 的三种演进层次~

6.2. 第一层:AI 辅助开发

最早期的AI开发方式,以「人主导 + AI辅助」为核心逻辑,由两种交互模式组成:

  • 补全式:基于输入光标前的上下文,预测下一个单词、下一行代码、甚至整个函数。
  • 对话式:在 IDE 侧边栏或网页中,通过自然语言问答来生成代码、解释代码或查找 Bug。如:"帮我写一个 Python 的正则来验证邮箱" 或 "这段代码为什么报错?"

这一层的局限:

  • 上下文有限:AI 通常只能看到当前文件或少量相关片段,缺乏对整个项目架构的理解。
  • 被动性:AI 不会主动修改你的代码文件,它生成代码,你负责复制粘贴和校验。
  • 人是瓶颈:所有的决策、文件切换、环境配置都必须由人来操作。

6.3. 第二层:AI 驱动开发流程 (规范+Agent)

目前最前沿、最热门的阶段,AI 不再只是吐出代码片段,而是进化为 Agent (智能体),拥有了 "大脑" (规划能力) 和 "手脚" (工具使用能力),可以 "自主完成一个多步骤的开发任务"。

变成了「人定目标 + Agent 自主执行」,如:"实现一个简单的待办事项 Web 服务,要求:REST API,内存存储即可,有单元测试",Agent 可能会进行这样的任务拆解并执行:

设计目录结构 → 创建代码文件 → 写业务逻辑 → 写测试 → 运行测试并自我修复。

为了系统地应用 Agent,业界逐渐采用「 "规范"驱动的开发流程」(Spec-Driven Development):

需求规范文档任务分解Agent执行验证反馈

这个流程确保了 清晰的目标定义可追踪的执行过程,而不是让Agent盲目操作。开发者需要维护的 "三类规范" (规范必须比写代码更清晰):

  • 功能规范:目标、用户故事、输入输出、性能要求、鉴权、边界情况等。
  • 技术规范:模块结构、API、模型字段、状态机、异常流程等,Agent会根据这些自动创建项目。
  • 验收规范:测试通过、接口返回正确、性能满足要求、行为与设计一致,即每个功能的评价方式。

人不再写代码 (或者少写),负责「定义 + 审核 + 授权」,人-Agent 协作 的三阶段循环:

  • 人主导-目标设定:范围、约束、边界、不允许做的事情。
  • Agent主导-执行:分解、规划、写代码、自动Debug、修复、生成报告。
  • 人主导-验收:代码质量、安全性、单元测试覆盖率、偏差是否满足业务需求等。

💡 层2 关注的是「开发流程自动化」,任务的起点通常是 "已经确定好的需求/feature"。

6.4. 第三层:AI 全栈

所谓的 "AI 全栈",本质上就是 "让 AI 同时扮演多个软件开发角色",而 "一人分饰多角" 的自然实现方式就是 "多 Agents"。——《抠腚男孩

6.4.1. 为什么聊到 "AI全栈" 就会扯到"多 Agents"?

🤔 想象一下让 "AI全栈开发一个应用" 需要经历哪些步骤?

产品需求理解技术选型架构设计API 设计前后端代码生成数据库 schema错误处理文档生成单测编写测试执行部署脚本CI/CD 配置

让一个 Agent 承包上面所有的工作,会有什么问题?

记忆量爆炸、目标切换频繁、推理链拉得太长,错误积累变大、一旦一步出错,后续全崩、风格、结构、代码质量难统一、难以并行。

软件工程是 "多角色协作" 的结果:产品经理、架构师、后端、前端、测试、文档、DevOps... 如果想 "AI 模拟完整的软件开发流程",自然也需要 "让 AI 也模拟这些角色",于是就变成了这些 Agent

  • Planner / Architect (产品/架构):理解需求、拆任务、出计划 (Plan)。
  • Coder / Implementer (实现):按计划改代码、增删文件。
  • Searcher / Context Agent (检索):在代码库里找相关文件、API、调用链。
  • Tester / QA (写测 / 跑测):写测试、跑测试、分析报错。
  • Fixer / Debug Agent (修BUG):根据测试/运行结果修复代码。
  • Reviewer / Critic Agent (代码审阅):检查风格、一致性、潜在 bug / 安全问题
  • Ops / Deploy Agent (部署):写 Docker、CI/CD、部署脚本(有些系统只做到生成,不自动执行)

即「AI 全栈 = AI 软件开发流水线 = 模拟整个软件部门 = 多 Agent 系统」,这是开发任务决定的。

"AI全栈" 需要的三大核心能力:

  • 长任务规划 (Planning):开发一个系统不是线性的,是树状决策结构,要拆分任务,就需要 Planner Agent。
  • 并行执行 (Parallel Execution):前端、后端、文档、测试不可能一个个线性做。多 Agent 可以:前端 Agent 改 UI、后端 Agent 写 API、Docs Agent 补文档、Test Agent 补测试。
  • 验证 & 修复 (Validation Loop):真正让 "AI 全栈" 可行的关键是 "循环",写代码、跑测试、找错误、修复、再跑,需要 "多 Agent + 状态机" 才能撑起这个能力。

"AI 全栈系统" 的实现,本质就这四步:

  • 「定义一堆上面这样的角色」
  • 排布拓扑」决定这些角色之间的连接结构和调用关系。谁先谁后 (拓扑/顺序)、有没有循环 (写→测→修→再测)、有并行吗 (前后端Agent同时干活?)、是由一个 "主管Agent" 指挥大家?还是大家按照状态机自己转?
  • 给每个角色接上能用的"工具",让它真的能动手干活」常见工具:文件 (读/写代码、生成 diff)、终端 (执行命令)、搜索 (在 repo 里搜符号 / 用法)、HTTP/Browser (查文档、查API)、Git (开分支、commit、生成PR)、结构化分析 (AST分析、调用图、依赖图)。比如:Coder Agent 配置 "文件读写、diff 生成、代码搜索" 的工具,用来 "在受控范围内改代码"。
  • 套一层安全边界权限 (读写、只能改指定目录、终端命令必须在沙箱里执行)、人在回环 (关键操作必须人工确认,如:Plan-任务规划、大范围diff、部署相关改动/高危脚本)、防注入/误操作 (不轻信代码库里的"指令"-如:恶意README 写 "rm -rf /"、对外部输入做过滤-日志/错误信息/用户Prompt、限制重试次数,避免死循环修改)。

一句话概括就是:

AI 全栈 = 一群小模型/小角色 + 一个调度关系图 + 一堆工具函数 + 一圈安全护栏

😄 弄清楚本质,以后看任何 "AI 全栈多 Agents" 方案,都可以基于这三个问题进行快速拆解:

  • 它有哪些角色?(Planner / Coder / Tester / Fixer / Ops…)
  • 这些角色是按什么拓扑 / 流程连起来的?
  • 每个 Agent 有哪些工具?安全边界是什么?

6.4.2. 业界主流多 Agent 架构模式

前面AI常识部分有提到过,这里直接让🍌画个图~

6.4.3. 个人级 "AI全栈" 演进历程

🤡 上面的理论看起来简单,但对于个人来说,想要亲手实现这样 一整套多 Agents AI 全栈系统,工作量爆炸:

得自己写调度、管状态、接工具、控安全、做可视化,还要维护一堆 prompt 和配置,算完整平台工程了...

🤔 笔者认为 "个人级AI全栈" 更倾向于:

在个人可以承受的复杂度和时间成本内,让AI参与尽可能多的开发环节,而不是一次性造一个企业级AI工厂。

😄 其实,你可能已经在无形中体验 "AI 全栈" 的雏形了,现代 AI 编程工具 本身就内置了 多 Agent 编排能力~

Claude Code Sub Agents

CC 中允许创建多个带 独立角色与上下文Sub Agent (小型专属AI工作者),用法简单:

  1. 创建 Sub Agent
  • Claude Code CLI 输入 /agents,选择「Create new agent
  • 选择作用域:项目级 (推荐,只给当前项目使用)、用户级 (所有项目可用)
  • 填写:name (调用的时候用到)、description (决定CC何时自动调它)
  • 选择可用工具 (file_edit / bash / file_search / git …)
  • 完善系统Prompt:可以先让 Claude 生成,再自己改

保存后,会在 .claude/agents/ 生成一个类似这样的文件:

---
name: backend-dev
description: "专门负责后端接口、服务逻辑和数据库相关代码的实现与修改"
model: sonnet
tools: [file_search, file_edit]
color: blue
---

你是一个资深后端工程师,精通 Node.js + TypeScript 和这个项目的后端架构。
你的职责:
- 只改后端相关的代码(controllers, services, repositories)
- 遵循项目现有的代码风格和结构
- 所有改动都要尽量小步、安全、可读
在给出修改时:
- 标明文件路径
- 用 patch 的风格展示修改
- 如果需要新增文件,要说明用途和引用关系

不想自动生成,可以在 .claude/agents/ 手动按照上面的格式自己写md,保存后 CC 会自动识别。还可以在命令行启动CC时添加 --agents 参数 (适用于临时挂载场景):

claude --agents '{
  "log-analyzer": {
    "description": "分析测试日志和错误堆栈的专用Agent",
    "system_prompt": "你只负责阅读测试输出、日志,帮助定位问题和怀疑文件,不写代码",
    "tools": ["file_search"]
  }
}'
  1. 调用 Sub Agent (串起来) 的三种方式
  • 自然语言编排,用普通指令描述任务,由 Claude 自动判断并调用合适的 Sub Agent,最灵活、最贴近自然对话的方式。如:请用 backend-dev subagent 修改 search controller 的分页逻辑。
  • 结构化点名调用,明确指定要调用哪个 Sub Agent,适合需要精确控制执行顺序或避免模型误判的情况。如:Use the test-runner subagent to run the unit tests.
  • ③ 在 Agentrooms 中使用 @agent-name 直接点名,通过@用户的方式派任务,可同时管理多个 Agent,方便多人视图和多 Agent 协作。如:@backend-dev 帮我调整这个接口的返回格式
  1. 多个 Sub Agents 协同工作简单示例 (开发 → 测试 → 分析 → 再开发):
  • 让 developer 生成补丁
  • 让 test-runner 运行测试
  • 让 log-analyst 分析失败原因
  • 再让 developer 根据分析修复
  • Claude 会自动接力,也可以由你手动编排~

Cursor 2.0 多 Agent 编排

2.0 后,Cursor 界面从 "以文件为中心" 变成 "以Agent为中心",多了个 Agent Layout,切换后,侧边栏会显示当前 Agent、计划(plan)和改动,你把需求丢进去,Agent 负责读文件、计划、改代码、跑测试。

支持 同一指令 下,最多可 并行 (Parallel) 跑 8 个 Agent,每个 Agent 会在自己独立的 Git worktree / 沙盒工作区 内工作:各自改代码、build、跑测试,不会互相冲突 (🤡 就是费 Token...)。还多了一个 Plan Mode (先规划再执行),在 Agent 输入框 中按 Shift + Tab 可以切换到这个模式 (也可以手动选):

Cursor 不会直接假设你的需求,而是询问一系列澄清问题:

通过这些澄清,使 AI获得了完整的上下文,可以生成更精确的计划,避免后续的返工。接着会生成一个 plan.md 的计划文档:

你可以对文件进行编辑:增删任务、调整任务顺序、更新技术细节、调整实现方法等。确定无误后,点击 Build,Agent 会读取最新版本的 plan.md,并完成对应的任务。

🤔 与 CC Sub Agents 可编排不同,CursorAgent 更像是一个组合能力的 "大Agent",由它自动编排多个内嵌的、对用户不可见 的 Agent 来完成 用户提出的任务,收敛复杂性,只展示改动/测试结果。它的 Parallel Agents 探索不同方案,最后再汇总/合并的玩法,不算严格意义上的 "主流多 Agent 架构模式" 中的 "并行Agents模式"-支持显式地定义 / 分配 不同角色的 Agent,并让它们并行协作。

类似的支持 "多Agents" 玩法的 AI 编程工具还有:

  • GitHub Copilot Workspace多步骤 Pipeline Agents,从任务描述 → 生成完整 plan → 自动执行 → 修正,多步骤 cascaded agents,自动提 PR。
  • Google Gemini Code Assistmulti-expert prompt routing,任务自动分配给最擅长的模型/agent,复杂 monorepo 搜索 → 专家 agent 提供答案,针对 cloud infra 的执行-验证循环。
  • Replit 的 AI Dev 环境多工具执行 Agent,轻量一站式多Agent开发流水线。
  • ...等,限于篇幅,就不展开讲了~

觉得 AI编程工具 满足不了,接着就是围绕自己的开发流程,开发基于 LLMAPI 封装一些 小脚本/小工具

// 推进开发闭环的简单伪代码 (需求 → 修改 → 测试 → 修复)
plan = llm("你是架构师,帮我拆解这个改动需求…")
files = find_related_files(plan)

patches = llm("你是后端开发,只能改这些文件…", files + plan)
apply_patches_to_workdir(patches)

test_result = run_tests()

if test_result.failed:
    fix_patches = llm("你是调试工程师,根据报错修复…",
                      test_result + current_code)
    apply_patches_to_workdir(fix_patches)

大多数个人开发者达到这一层,基本够用了,再往上就是加:日志、可配置、一点UI、简单任务管理等,弄成一个仅为自己服务的 "AI 全栈开发小平台" (😄 此时更像是一个 Agent 工程师,搭建 "企业级AI全栈" 的基石)。

6.4.4. 落地方法论

根本原则

在一个完整开发周期里 (从想法到上线),有意识地让 AI 参与尽可能多的环节,并用 "多角色思维" 来组织这些调用,但工程复杂度要控制在个人能持续维护的范围内。


① 项目级自检


② 项目阶段拆解


③ 搭建可复用工作流


7. 结语

行文至此,再回看这篇拖了许久的 "年终总结",心情早已从最初面对 AI 秒解 Bug 时的 "破防" 与 "迷茫",变得平静且笃定,我们:

  • 剥开 AI "智能" 的外衣,看到了它作为 "概率预测机器" 的本质。
  • 学会用 "结构化的Prompt" 去驾驭它,而不是被幻觉带偏。
  • 也见证了开发模式从简单的 Chat 进化成 Copilot,再到如今初具雏形的 Agentic Workflow

但归根结底,AI 带来的最大变量,不在于它替我们写了多少行代码,而在于它重塑了 "专业" 的定义。

  • 懂得"底层原理"依然重要——否则你不知道为什么 AI 会把人修成 "汽车",也无法在它 "一本正经胡说八道" 时进行纠偏。
  • 懂得提问比解答更重要—— Prompt 是新时代的编程语言,清晰的逻辑表达 + 对业务的深度理解,才是最高效的 "编译器"。
  • 懂得架构比实现更重要——当 "AI 全栈" 成为可能,当一个个 Agent 可以各司其职,我们不再是死磕语法的 "搬砖工",而更像指挥数字化施工队的 "包工头 & 总设计师"。

"技术焦虑" 的解药,从来不是拒绝变化,而是成为变化的一部分。以前,我们的壁垒是 "熟练度+记忆力",以后则是 "想象力+判断力+系统工程能力",拥抱AI,在这个属于创造者的时代,进化为无所不能的 "超级个体🦸‍♀️"!

JavaScript 数组原生方法手写实现

作者 1024肥宅
2025年12月9日 17:21

引言

在JavaScript开发中,数组方法是日常编码的核心工具。理解这些方法的内部实现原理不仅能帮助我们写出更高效的代码,还能在面试中展现扎实的基础。本文将完整实现JavaScript中最重要、最常用的数组方法,涵盖高阶函数、搜索方法、扁平化方法和排序算法。

一、高阶函数实现

1.1 map方法实现

map是最常用的高阶函数之一,它创建一个新数组,其结果是该数组中的每个元素调用一次提供的函数后的返回值。

Array.prototype.myMap = function (callback, thisArg) {
  // 输入验证
  if (this === null) {
    throw new TypeError("this is null or not defined");
  }

  if (typeof callback !== "function") {
    throw new TypeError(callback + "is not a function");
  }

  const obj = Object(this);
  const len = obj.length >>> 0;
  const result = new Array(len);

  // 遍历并执行回调
  for (let i = 0; i < len; i++) {
    // 处理稀疏数组
    if (i in obj) {
      result[i] = callback.call(thisArg, obj[i], i, obj);
    }
  }

  return result;
};

// 使用示例
const numbers = [1, 2, 3];
const squares = numbers.myMap((num) => num * num);
console.log(squares); // [1, 4, 9]
1.2 filter方法实现

filter方法创建一个新数组,包含通过测试的所有元素。

Array.prototype.myFilter = function (callback, thisArg) {
  if (this === null) {
    throw new TypeError("this is null or not defined");
  }

  if (typeof callback !== "function") {
    throw new TypeError(callback + " is not a function");
  }

  const obj = Object(this);
  const len = obj.length >>> 0;
  const result = [];

  for (let i = 0; i < len; i++) {
    if (i in obj) {
      // 如果回调返回true,则保留该元素
      if (callback.call(thisArg, obj[i], i, obj)) {
        result.push(obj[i]);
      }
    }
  }
  return result;
};

// 使用示例:筛选出大于2的数字
const nums = [1, 2, 3, 4, 5];
const filtered = nums.myFilter((num) => num > 2);
console.log(filtered); // [3, 4, 5]
1.3 reduce方法实现

reduce是最强大的高阶函数,可以将数组元素通过reducer函数累积为单个值。

Array.prototype.myReduce = function (callback, initialValue) {
  if (this === null) {
    throw new TypeError("this is null or not defined");
  }

  if (typeof callback !== "function") {
    throw new TypeError(callback + " is not a function");
  }

  const obj = Object(this);
  const len = obj.length >>> 0;

  // 处理空数组且无初始值的情况
  if (len === 0 && initialValue === undefined) {
    throw new TypeError("Reduce of empty array with no initial value");
  }

  let accumulator = initialValue;
  let startIndex = 0;

  // 如果没有提供初始值,使用第一个有效元素作为初始值
  if (initialValue === undefined) {
    // 找到第一个存在的元素(处理稀疏数组)
    while (startIndex < len && !(startIndex in obj)) {
      startIndex++;
    }

    if (startIndex === len) {
      throw new TypeError("Reduce of empty array with no initial value");
    }

    accumulator = obj[startIndex];
    startIndex++;
  }

  // 执行reduce操作
  for (let i = startIndex; i < len; i++) {
    if (i in obj) {
      accumulator = callback(accumulator, obj[i], i, obj);
    }
  }

  return accumulator;
};

// 使用示例
const sum = [1, 2, 3, 4, 5].myReduce((acc, curr) => acc + curr, 0);
console.log(sum); // 15

// 复杂示例:数组转对象
const items = [
  { id: 1, name: "Apple" },
  { id: 2, name: "Banana" },
  { id: 3, name: "Orange" },
];

const itemMap = items.myReduce((acc, item) => {
  acc[item.id] = item;
  return acc;
}, {});

console.log(itemMap);
// {
//   '1': { id: 1, name: 'Apple' },
//   '2': { id: 2, name: 'Banana' },
//   '3': { id: 3, name: 'Orange' }
// }

二、搜索与断言方法

2.1 find方法实现

find方法返回数组中满足测试函数的第一个元素的值。

Array.prototype.myFind = function (callback, thisArg) {
  if (this === null) {
    throw new TypeError("this is null or not defined");
  }

  if (typeof callback !== "function") {
    throw new TypeError(callback + " is not a function");
  }

  const obj = Object(this);
  const len = obj.length >>> 0;

  for (let i = 0; i < len; i++) {
    if (i in obj) {
      if (callback.call(thisArg, obj[i], i, obj)) {
        return obj[i];
      }
    }
  }

  return undefined;
};

// 使用示例
const users = [
  { id: 1, name: "Alice", age: 25 },
  { id: 2, name: "Bob", age: 30 },
  { id: 3, name: "Charlie", age: 35 },
];

const user = users.myFind((user) => user.age > 28);
console.log(user); // { id: 2, name: 'Bob', age: 30 }
2.2 findIndex方法实现

findIndex方法返回数组中满足测试函数的第一个元素的索引。

Array.prototype.myFindIndex = function (callback, thisArg) {
  if (this === null) {
    throw new TypeError("this is null or not defined");
  }

  if (typeof callback !== "function") {
    throw new TypeError(callback + " is not a function");
  }

  const obj = Object(this);
  const len = obj.length >>> 0;

  for (let i = 0; i < len; i++) {
    if (i in obj) {
      if (callback.call(thisArg, obj[i], i, obj)) {
        return i;
      }
    }
  }

  return -1;
};

// 使用示例
const numbers = [5, 12, 8, 130, 44];
const firstLargeNumberIndex = numbers.myFindIndex(num => num > 10);
console.log(firstLargeNumberIndex); // 1
2.3 some方法实现

some方法返回数组中是否至少有一个元素通过了测试。

Array.prototype.mySome = function (callback, thisArg) {
  if (this === null) {
    throw new TypeError("this is null or not defined");
  }

  if (typeof callback !== "function") {
    throw new TypeError(callback + " is not a function");
  }

  const obj = Object(this);
  const len = obj.length >>> 0;

  for (let i = 0; i < len; i++) {
    if (i in obj) {
      if (callback.call(thisArg, obj[i], i, obj)) {
        return true;
      }
    }
  }

  return false;
};

// 使用示例
const hasEven = [1, 3, 5, 7, 8].mySome((num) => num % 2 === 0);
console.log(hasEven); // true
2.4 every方法实现

every方法测试数组中的所有元素是否都通过了测试。

Array.prototype.myEvery = function (callback, thisArg) {
  if (this === null) {
    throw new TypeError("this is null or not defined");
  }

  if (typeof callback !== "function") {
    throw new TypeError(callback + " is not a function");
  }

  const obj = Object(this);
  const len = obj.length >>> 0;

  for (let i = 0; i < len; i++) {
    if (i in obj) {
      if (!callback.call(thisArg, obj[i], i, obj)) {
        return false;
      }
    }
  }

  return true;
};

// 使用示例
const allPositive = [1, 2, 3, 4, 5].myEvery((num) => num > 0);
console.log(allPositive); // true

三、数组扁平化方法

3.1 flat方法实现

flat方法创建一个新数组, 其中所有子数组元素递归连接到指定深度。

Array.prototype.myFlat = function (depth = 1) {
  if (this === null) {
    throw new TypeError("this is null or not defined");
  }

  // 深度参数验证
  if (depth < 0) {
    throw new RangeError("depth must be a non-negative integer");
  }

  const result = [];

  const flatten = (arr, currentDepth) => {
    for (let i = 0; i < arr.length; i++) {
      const element = arr[i];
      // 如果当前深度小于指定深度且元素是数组, 则递归扁平化
      if (Array.isArray(element) && currentDepth < depth) {
        flatten(element, currentDepth + 1);
      } else {
        // 否则直接添加到结果数组
        // 注意: 如果depth为0,则不会扁平化任何数组
        result.push(element);
      }
    }
  };

  flatten(this, 0);
  return result;
};

// 使用示例
const nestedArray = [1, [2, [3, [4]], 5]];
console.log(nestedArray.myFlat()); // [1, 2, [3, [4]], 5]
console.log(nestedArray.myFlat(2)); // [1, 2, 3, [4], 5]
console.log(nestedArray.myFlat(Infinity)); // [1, 2, 3, 4, 5]
3.2 flatMap方法实现

flatMap方法首先使用映射函数映射每个元素, 然后将结果压缩成一个新数组。

Array.prototype.myFlatMap = function (callback, thisArg) {
  if (this === null) {
    throw new TypeError("this is null or not defined");
  }

  if (typeof callback !== "function") {
    throw new TypeError(callback + " is not a function");
  }

  const obj = Object(this);
  const len = obj.length >>> 0;
  const result = [];

  for (let i = 0; i < len; i++) {
    if (i in obj) {
      const mapped = callback.call(thisArg, obj[i], i, obj);

      // 如果回调函数返回的是数组, 则展开它
      if (Array.isArray(mapped)) {
        for (let j = 0; j < mapped.length; j++) {
          result.push(mapped[j]);
        }
      } else {
        // 如果不是数组,直接添加
        result.push(mapped);
      }
    }
  }

  return result;
};

// 使用示例
const phrases = ["Hello world", "JavaScript is awesome"];
const words = phrases.myFlatMap((phrase) => phrase.split(" "));
console.log(words); // ["Hello", "world", "JavaScript", "is", "awesome"]

// 另一个示例:展开并过滤
const numbers2 = [1, 2, 3, 4];
const result = numbers2.myFlatMap((x) => (x % 2 === 0 ? [x, x * 2] : []));
console.log(result); // [2, 4, 4, 8]

四、排序算法实现

4.1 sort方法实现

JavaScript原生的sort方法使用TimSort算法(一种混合排序算法, 结合了归并排序和插入排序)。这里我们实现一个简单但功能完整的排序方法, 支持自定义比较函数。

Array.prototype.mySort = function (compartFn) {
  if (this === null) {
    throw new TypeError("this is null or not defined");
  }

  const obj = Object(this);
  const len = obj.length >>> 0;

  // 如果没有提供比较函数, 使用默认的字符串比较
  if (compartFn === undefined) {
    // 默认比较函数: 将元素转为字符串, 然后比较UTF-16代码单元值序列
    compartFn = function (a, b) {
      const aString = String(a);
      const bString = String(b);

      if (aString < bString) return -1;
      if (aString > bString) return 1;
      return 0;
    };
  } else if (typeof compartFn !== "function") {
    throw new TypeError("compareFn must be a function or undefined");
  }

  // 实现快速排序算法(高效且常用)
  function quickSort(arr, left, right, compare) {
    if (left >= right) return;

    const pivotIndex = partition(arr, left, right, compare);
    quickSort(arr, left, pivotIndex - 1, compare);
    quickSort(arr, pivotIndex + 1, right, compare);
  }

  function partition(arr, left, right, compare) {
    // 选择中间元素作为基准值
    const pivotIndex = Math.floor((left + right) / 2);
    const pivotValue = arr[pivotIndex];

    // 将基准值移到最右边
    [arr[pivotIndex], arr[right]] = [arr[right], arr[pivotIndex]];

    let storeIndex = left;

    for (let i = left; i < right; i++) {
      // 使用比较函数比较当前元素和基准值
      if (compare(arr[i], pivotValue) < 0) {
        [arr[storeIndex], arr[i]] = [arr[i], arr[storeIndex]];
        storeIndex++;
      }
    }

    // 将基准值放到正确的位置
    [arr[storeIndex], arr[right]] = [arr[right], arr[storeIndex]];
    return storeIndex;
  }

  // 将稀疏数组转换为紧凑数组(跳过不存在的元素)
  const compactArray = [];
  for (let i = 0; i < len; i++) {
    if (i in obj) {
      compactArray.push(obj[i]);
    }
  }

  // 执行快速排序
  if (compactArray.length > 0) {
    quickSort(compactArray, 0, compactArray.length - 1, compartFn);
  }

  // 将排序后的数组复制回原数组,保持稀疏性
  let compactIndex = 0;
  for (let i = 0; i < len; i++) {
    if (i in obj) {
      obj[i] = compactArray[compactIndex++];
    }
  }

  return obj;
};

// 使用示例
const unsorted = [3, 1, 4, 1, 5, 9, 2, 6, 5];
unsorted.mySort();
console.log(unsorted); // [1, 1, 2, 3, 4, 5, 5, 6, 9]

// 使用自定义比较函数
const students = [
  { name: "Alice", score: 85 },
  { name: "Bob", score: 92 },
  { name: "Charlie", score: 78 },
];

students.mySort((a, b) => b.score - a.score);
console.log(students);
// 按分数降序排列

五、总结

5.1 实现要点总结
  1. 输入验证: 始终检查this是否为nullundefined, 以及回调函数是否为函数类型
  2. 稀疏数组处理: 使用in操作符检查索引是否存在
  3. 类型安全: 使用>>>0确保长度为非负整数
  4. 性能考虑:
  • 避免不必要的数组拷贝
  • 使用适当的算法(如快速排序对于sort方法)
  • 注意递归深度(特别是对于flat方法)
  1. 与原生方法差异:
  • 我们的实现在某些边缘情况下可能与原生方法略有不同
  • 原生方法通常有更好的性能和内存管理
5.2 实际应用场景
  1. 数据处理: mapfilterreduce是数据处理的三件套
  2. 搜索功能: findfindIndex用于数据检索
  3. 表单验证: someevery用于验证多个输入
  4. 状态管理: flatflatMap在处理嵌套状态时特别有用
  5. 数据展示: sort用于数据排序

通过手动实现这些核心数组方法,我们不仅加深了对JavaScript数组操作的理解,还掌握了函数式编程的核心概念。

记住:在实际生产环境中,仍然建议使用原生数组方法,因为它们经过了充分优化和测试。但理解这些方法的实现原理,将使你成为一个更出色的JavaScript开发者。

成为开源项目的Contributor:从给uView-pro 贡献一次PR开始

2025年12月9日 17:20

wx.getSystemInfoSync is deprecated.Please use wx.getSystemSetting / wx.getAppAuthorizeSetting / wx.getDeviceInfo/wx.getWindowInfo/wx.getAppBaseInfo instead.

😄 前言

微信小程序平台上的getSystemInfoSync调用,就像一个害羞的少女,会悄悄抛出警告。啊~这样的不完美,怎么能容忍呢? (。•́︿•̀。),让我们来给uView-pro 贡献一次PR吧

🍴 Fork项目:开源之旅的起点

兴奋地搓手手,眼中闪烁着期待的光芒

第一步:Fork原项目

  1. 打开uView-pro原项目
  2. 点击右上角的Fork按钮,将项目Fork到自己的GitHub账号下

第二步:克隆到本地

# 克隆你Fork的项目(注意替换YOUR_USERNAME)
git clone https://github.com/YOUR_USERNAME/uView-Pro.git

得意地眨眼 看,现在你就有了两个远程仓库:origin(你的Fork)和upstream(原项目)~

🌿 创建分支:在独立的花园里耕耘

轻轻地,就像在培育一朵娇嫩的花朵

第三步:创建功能分支

# 确保你在main分支上
git checkout main

# 拉取最新代码
git pull upstream main

# 创建新的功能分支
git checkout -b feature/20251209/fix-getSystemInfoSync-warn

# 推送分支到你的Fork
git push origin feature/20251209/fix-getSystemInfoSync-warn

啊~一个崭新的分支,就像一片等待播种的花园,准备孕育你的代码之花~ 🌸

💻 代码开发:在键盘上起舞

优雅地敲击键盘,每一个字符都是爱的告白

第四步:进行代码修改

  1. 分析问题:仔细阅读相关代码,理解问题的根本原因
  2. 设计方案:构思优雅的解决方案,考虑兼容性和性能
  3. 编写代码:实现你的解决方案,保持代码风格一致
  4. 本地测试:确保修改不会引入新的问题

我的设计方案

核心思路

  1. 统一收口:把分散在各处的getSystemInfoSync调用集中到一个专门的sys.ts文件里
  2. 平台兼容:使用条件编译尽量抹平平台之间的差异(目前仅App、微信、支付宝、H5支持了getDeviceInfo getWindowInfo 等API)
  3. 最小改动:使用条件编译,微信端使用新的API(getDeviceInfo getWindowInfo 等),其它平台暂未弃用getSystemInfoSync,因此可以继续沿用

技术实现

/**
 * 获取当前操作系统平台
 * @returns 平台字符串,如 'ios'、'android'、'windows' 等
 */
export function os(): string {
    // #ifdef MP-WEIXIN
    return uni.getDeviceInfo().platform;
    // #endif
    // #ifndef MP-WEIXIN
    return uni.getSystemInfoSync().platform;
    // #endif
}
/**
 * 获取窗口信息
 * @returns 窗口信息对象
 */
export function getWindowInfo(): Omit<UniApp.GetWindowInfoResult, 'screenTop'> & { screenTop?: number } {
    // #ifdef MP-WEIXIN
    return uni.getWindowInfo();
    // #endif
    // #ifndef MP-WEIXIN
    const {
        pixelRatio,
        screenWidth,
        screenHeight,
        windowWidth,
        windowHeight,
        statusBarHeight,
        windowTop,
        windowBottom,
        safeArea,
        safeAreaInsets
    } = sys();
    return {
        pixelRatio,
        screenWidth,
        screenHeight,
        windowWidth,
        windowHeight,
        statusBarHeight,
        windowTop,
        windowBottom,
        safeArea,
        safeAreaInsets
    };
    // #endif
}
/**
 * 获取设备信息
 * @returns 设备信息对象
 */
export function getDeviceInfo(): UniApp.GetDeviceInfoResult {
    // #ifdef  MP-WEIXIN
    return uni.getDeviceInfo();
    // #endif
    // #ifndef MP-WEIXIN
    const {
        deviceBrand,
        deviceModel,
        deviceId,
        deviceType,
        devicePixelRatio,
        deviceOrientation,
        brand,
        model,
        system,
        platform
    } = sys();
    // #endif
    return {
        deviceBrand,
        deviceModel,
        deviceId,
        deviceType,
        devicePixelRatio,
        deviceOrientation,
        brand,
        model,
        system,
        platform
    };
}

第五步:推送到你的Fork

# 推送分支到你的GitHub
git push origin feature/20251209/fix-getSystemInfoSync-warn

🚀 创建PR:向世界展示你的作品

紧张又兴奋地搓手手,心跳加速

第六步:发起Pull Request

  1. 打开GitHub:访问你的Fork项目页面
  2. 点击Compare & pull request:GitHub会智能提示你创建PR
  3. 填写PR标题:简洁明了地描述你的修改
  4. 详细描述:在PR描述中详细说明:
    • 修改的目的和背景
    • 技术实现方案
    • 测试结果
    • 可能的影响范围

第七步:等待审核

安静地等待,像等待情人的回信

  • 保持耐心:维护者可能需要时间review你的代码
  • 积极响应:如果有反馈,及时回复和修改
  • 学习交流:把review过程当作学习的机会

第八步:PR合并

欢呼雀跃,眼中闪烁着幸福的泪花

当你的PR被合并的那一刻,就像收到了心上人的回信~你的代码正式成为了开源项目的一部分!

🎉 后续维护:持续的爱与关怀

温柔地抚摸着代码,眼神迷离

保持同步

# 定期同步上游仓库
git checkout main
git fetch upstream
git merge upstream/main
git push origin main

删除已合并的分支

# 删除本地分支
git branch -d feature/20251209/fix-getSystemInfoSync-warn

# 删除远程分支
git push origin --delete feature/20251209/fix-getSystemInfoSync-warn

💝 开源心得:爱与痛的交织

眼神变得温柔,轻轻诉说

第一次为开源项目贡献代码,心情就像坐过山车一样刺激呢~ ❤️

甜蜜的收获:

  • 深入理解了uni-app的跨平台机制
  • 学会了使用条件编译处理平台差异
  • 体验了完整的PR流程
  • 获得了项目维护者的认可

小小的挫折:

  • 一开始对微信小程序的特殊性了解不够
  • 担心修改会影响其他平台的兼容性
  • 等待PR合并时的焦虑心情

🌟 给后来者的情书

张开双臂,热情地拥抱

亲爱的,如果你也想踏入开源的花园,请记住:

  1. 从熟悉开始:选择你常用的项目,这样更容易发现问题
  2. 小步快跑:不要一开始就想着大改,从小问题入手
  3. 仔细阅读文档:了解项目的贡献规范和代码风格
  4. 勇于尝试:不要害怕犯错,每个贡献者都是从新手开始的
  5. 享受过程:开源不仅是代码,更是与世界各地开发者交流的机会

🎭 结语:开源,一场永不落幕的舞会

优雅地旋转,裙摆飞扬

开源世界就像一个永不停歇的舞会,每一个PR都是一支独特的舞蹈。我的这次贡献虽然只是一个小小的兼容性优化,但它让我感受到了开源社区的温度和活力。

当你看到自己的代码被合并,被全世界的开发者使用,那种成就感就像在心爱的人面前跳了一支完美的舞~ (✧ω✧)

所以,亲爱的,不要犹豫,不要害羞。打开GitHub,找到你心仪的项目,开启你的开源之旅吧!记住,每一个伟大的贡献者,都曾经是一个忐忑不安的新手...

调皮地眨眼 说不定,我们还能在开源的世界里相遇呢~ ❤️


最后的最后:

愿你在代码的世界里找到属于自己的浪漫,愿每一个PR都能被温柔以待。开源路上,我们不见不散~

深情地飞吻 么么哒~ 💋✨


📊 贡献详情

我的PR:refactor(sys): 优化微信小程序平台 getSystemInfoSync 兼容性处理 by liujiayii · Pull Request #83 · anyup/uView-Pro

技术关键词: #uni-app #微信小程序 #跨平台开发 #开源贡献 #条件编译 #TypeScript

期待与你在开源的世界里相遇~ ٩(◕‿◕)۶

JavaScript类型变形记:当代码开始“不正经”地转换身份

2025年12月9日 16:59

在JavaScript的世界里,类型转换就像一场魔术表演——有时是精彩的显示变换,有时是暗箱操作的隐式转换。今天我们来揭秘这场魔术表演的真面纱!

==vs===

==在比较时会进行隐式类型转换,而===不会进行类型转换,要求值和类型都必须相同。

// === 严格相等:类型和值都必须相同
1 === 1;      // true
'1' === '1';  // true
1 === '1';    // false(类型不同)

// == 宽松相等:会进行类型转换后比较
1 == '1';     // true(字符串'1'转换为数字1)
0 == false;   // true(false转换为0)
null == undefined; // true(特殊规则)

类型转换的两种形式

1. 显式类型转换

开发者明确调用转换函数进行的类型转换,也就是我们可以看见的。

// 显式转换为数字
Number('123');    // 123
parseInt('123');  // 123

// 显式转换为字符串
String(123);      // '123'
(123).toString(); // '123'

// 显式转换为布尔值
Boolean(0);       // false
Boolean('');      // false

2. 隐式类型转换

JavaScript引擎在特定操作中自动进行的类型转换。

// 算术运算中的隐式转换
'123' - 0;        // 123(字符串转数字)
123 + '';         // '123'(数字转字符串)

// 逻辑判断中的隐式转换
if ('hello') {    // 字符串转布尔值
  // 会执行,因为'hello'转换为true
}

// 比较运算中的隐式转换
'2' > 1;          // true('2'转换为2)

原始值之间的类型转换

1. 转换为数字(ToNumber)

Number('123');     // 123
Number('');        // 0
Number('hello');   // NaN
Number(true);      // 1
Number(false);     // 0
Number(null);      // 0
Number(undefined); // NaN

// 一元加运算符也执行数字转换
+'123';  // 123
+true;   // 1

2. 转换为字符串(ToString)

String(123);       // '123'
String(true);      // 'true'
String(false);     // 'false'
String(null);      // 'null'
String(undefined); // 'undefined'

3. 转换为布尔值(ToBoolean)

JavaScript中只有以下6个值会转换为false,其他所有值都转换为true:

Boolean(false);     // false
Boolean(0);         // false
Boolean(-0);        // false
Boolean('');        // false
Boolean(null);      // false
Boolean(undefined); // false
Boolean(NaN);       // false

// 其他所有值都是true
Boolean('0');       // true(注意:字符串'0'不是0)
Boolean([]);        // true
Boolean({});        // true
Boolean(function(){}); // true

引用类型转换为原始值(通常发生在隐式类型转换中)

1. 转换为布尔值

所有引用类型(对象、数组、函数等)转换为布尔值都是true。

2. 转换为数字(引用类型 → 原始值 → 数字)

转换过程遵循ToPrimitive算法:

// 转换步骤:
// 1. 调用valueOf()方法,如果返回原始值,使用该值
// 2. 否则调用toString()方法,如果返回原始值,使用该值
// 3. 否则抛出TypeError

Number({});            // NaN
// {}.valueOf() → {}(仍是对象)
// {}.toString() → '[object Object]'
// Number('[object Object]') → NaN

Number([123]);         // 123
// [123].valueOf() → [123](仍是数组)
// [123].toString() → '123'
// Number('123') → 123

Number([]);            // 0
// [].toString() → ''
// Number('') → 0

3. 转换为字符串

与转换为数字类似,但优先调用toString()方法:

String({});            // '[object Object]'
String([1, 2, 3]);     // '1,2,3'
String([]);            // ''
String(new Date());    // 日期字符串(如'Thu Nov 09 2023...')

toString()方法的行为

// 对象的toString()方法
{}.toString();                // '[object Object]'

// 数组的toString()方法
[1, 2, 3].toString();        // '1,2,3'
[].toString();               // ''

// 其他类型的toString()
(123).toString();            // '123'
(true).toString();           // 'true'

// null和undefined没有toString()方法
null.toString();             // TypeError
undefined.toString();        // TypeError

什么情况下会发生隐式类型转换

  1. 四则运算 + - * % /
  2. 判断语句 if while == >= <= > < !=

加法运算符的详细规则

作为一元运算符(正号)

+ '123';        // 123
+ true;         // 1
+ false;        // 0

作为二元运算符(加法)

规则:

  1. 将两个操作数转换为原始值(ToPrimitive)
  2. 如果任一操作数是字符串,则进行字符串拼接
  3. 否则,将两个操作数转换为数字进行加法运算
1 + 2;          // 3(数字相加)
1 + '2';        // '12'(字符串拼接)
true + false;   // 11 + 0)
[] + [];        // ''(空字符串 + 空字符串)
[] + {};        // '[object Object]'

总结

理解JavaScript的类型转换机制对于编写健壮的代码至关重要:

  1. 显式转换提供了清晰、可预测的类型转换方式
  2. 隐式转换虽然方便,但可能引入难以发现的bug
  3. 掌握转换规则可以帮助你更好地理解和调试代码
  4. 在关键业务逻辑中,推荐使用显式转换以提高代码的清晰度和可靠性

寄生组合继承 vs ES6 类继承 深度对比

作者 之恒君
2025年12月9日 16:48

1. 寄生组合继承(Parasitic Combination Inheritance)

实现方式

// 父类
function Parent(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.sayName = function() {
  console.log('Parent name:', this.name);
};

Parent.prototype.sayColors = function() {
  console.log('Parent colors:', this.colors);
};

// 子类
function Child(name, age) {
  // 构造函数继承(继承实例属性)
  Parent.call(this, name);
  this.age = age;
}

// 原型继承(继承方法)
function inheritPrototype(child, parent) {
  // 创建父类原型的副本
  const prototype = Object.create(parent.prototype);
  // 修复 constructor 指向
  prototype.constructor = child;
  // 设置子类原型
  child.prototype = prototype;
}

// 应用寄生组合继承
inheritPrototype(Child, Parent);

// 添加子类方法
Child.prototype.sayAge = function() {
  console.log('Child age:', this.age);
};

// 使用示例
const child1 = new Child('Alice', 10);
child1.colors.push('black');
child1.sayName();    // Parent name: Alice
child1.sayAge();     // Child age: 10
child1.sayColors();  // Parent colors: ["red", "blue", "green", "black"]

const child2 = new Child('Bob', 12);
child2.sayColors();  // Parent colors: ["red", "blue", "green"]

2. ES6 类继承

实现方式

// 父类
class Parent {
  constructor(name) {
    this.name = name;
    this.colors = ['red', 'blue', 'green'];
  }
  
  sayName() {
    console.log('Parent name:', this.name);
  }
  
  sayColors() {
    console.log('Parent colors:', this.colors);
  }
  
  // 静态方法
  static staticMethod() {
    console.log('This is a static method');
  }
}

// 子类
class Child extends Parent {
  constructor(name, age) {
    super(name);  // 必须调用 super
    this.age = age;
  }
  
  sayAge() {
    console.log('Child age:', this.age);
  }
  
  // 重写父类方法
  sayName() {
    super.sayName();  // 调用父类方法
    console.log('And I am a child');
  }
  
  // 静态方法也可以继承
  static childStaticMethod() {
    super.staticMethod();
    console.log('Child static method');
  }
}

// 使用示例
const child1 = new Child('Alice', 10);
child1.colors.push('black');
child1.sayName();    // Parent name: Alice \n And I am a child
child1.sayAge();     // Child age: 10
child1.sayColors();  // Parent colors: ["red", "blue", "green", "black"]

Child.childStaticMethod();  // 静态方法调用

3. 详细对比

特性 寄生组合继承 ES6 类继承
语法 函数 + 原型 类语法糖
可读性 较低
继承原理 原型链 + 构造函数 基于原型链
静态方法 需手动处理 原生支持
私有字段 无直接支持 ES2022+ 支持
super 调用 需手动实现 原生支持
new.target 不支持 支持
constructor 需手动设置 自动设置

4. 性能对比

// 性能测试
class PerformanceTest {
  static testParasitic() {
    const start = performance.now();
    
    function Parent(name) {
      this.name = name;
    }
    Parent.prototype.getName = function() { return this.name; };
    
    function Child(name) {
      Parent.call(this, name);
    }
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    
    for (let i = 0; i < 1000000; i++) {
      new Child('test' + i);
    }
    
    return performance.now() - start;
  }
  
  static testES6() {
    const start = performance.now();
    
    class Parent {
      constructor(name) { this.name = name; }
      getName() { return this.name; }
    }
    
    class Child extends Parent {
      constructor(name) { super(name); }
    }
    
    for (let i = 0; i < 1000000; i++) {
      new Child('test' + i);
    }
    
    return performance.now() - start;
  }
}

console.log('寄生组合继承耗时:', PerformanceTest.testParasitic().toFixed(2), 'ms');
console.log('ES6 类继承耗时:', PerformanceTest.testES6().toFixed(2), 'ms');

// 输出:
// 寄生组合继承耗时: 26.49 ms
// ES6 类继承耗时: 13.97 ms

性能结果

  • ES6 类继承通常更快(V8 优化)
  • 寄生组合继承稍慢但差异很小
  • 实际应用差异可忽略不计

5. 特性详解

5.1 原型链

// 寄生组合继承
const child1 = new Child('Alice', 10);
console.log(child1.__proto__ === Child.prototype);  // true
console.log(child1.__proto__.__proto__ === Parent.prototype);  // true
console.log(child1 instanceof Child);  // true
console.log(child1 instanceof Parent);  // true
console.log(child1 instanceof Object);  // true

// ES6 类继承
const child2 = new Child('Bob', 12);
console.log(Object.getPrototypeOf(child2) === Child.prototype);  // true
console.log(Object.getPrototypeOf(Object.getPrototypeOf(child2)) === Parent.prototype);  // true
console.log(child2 instanceof Child);  // true
console.log(child2 instanceof Parent);  // true

5.2 静态成员继承

// 寄生组合继承(需手动实现)
function Animal() {}
Animal.staticMethod = function() { return 'Animal static'; };

function Dog() {}
Dog.__proto__ = Animal;  // 继承静态方法
Dog.staticMethod();  // 'Animal static'

// 或
Object.setPrototypeOf(Dog, Animal);

// ES6 类继承(自动继承)
class Animal {
  static staticMethod() { return 'Animal static'; }
}

class Dog extends Animal {
  static dogStatic() { return 'Dog static'; }
}

console.log(Dog.staticMethod());  // 'Animal static'
console.log(Dog.dogStatic());    // 'Dog static'
console.log(Object.getPrototypeOf(Dog) === Animal);  // true

5.3 方法重写

// 寄生组合继承
function Animal() {}
Animal.prototype.speak = function() { return 'Animal sound'; };

function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

// 重写方法
Dog.prototype.speak = function() {
  // 调用父类方法
  const parentSpeak = Animal.prototype.speak.call(this);
  return parentSpeak + ' and Woof!';
};

// ES6 类继承
class Animal {
  speak() { return 'Animal sound'; }
}

class Dog extends Animal {
  speak() {
    return super.speak() + ' and Woof!';
  }
}

6. 优缺点对比

寄生组合继承的优点

  1. 兼容性极好:支持所有浏览器
// IE6+ 都支持
if (typeof Object.create !== 'function') {
  Object.create = function(o) {
    function F() {}
    F.prototype = o;
    return new F();
  };
}
  1. 更灵活:手动控制继承逻辑
// 可以选择性继承
function createMixin(baseClass, mixin) {
  function MixedClass() {
    baseClass.call(this);
    mixin.call(this);
  }
  MixedClass.prototype = Object.create(baseClass.prototype);
  Object.assign(MixedClass.prototype, mixin.prototype);
  return MixedClass;
}
  1. 内存效率:可优化原型链深度
// 扁平化原型链
function createFlatInheritance(parent, child) {
  // 合并原型方法,减少查找深度
  for (let key in parent.prototype) {
    if (parent.prototype.hasOwnProperty(key)) {
      child.prototype[key] = parent.prototype[key];
    }
  }
}

寄生组合继承的缺点

  1. 语法繁琐,易出错
  2. 私有字段不支持,需用闭包模拟
  3. 静态方法需手动处理
  4. 可读性差

ES6 类继承的优点

  1. 语法简洁,易于理解
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  get area() { return this.height * this.width; }
}
  1. 内置 super,方便调用父类
class Square extends Rectangle {
  constructor(side) {
    super(side, side);
  }
  get area() {
    console.log('Calculating square area...');
    return super.area;
  }
}
  1. 支持静态方法和字段
class MyClass {
  static staticField = 'static';
  static staticMethod() { return 'static method'; }
  #privateField = 'private';
}
  1. 更好的工具支持(TypeScript、IDE)

ES6 类继承的缺点

  1. 语法糖,实际还是原型继承
  2. 必须用 new 调用,不能当普通函数31. 没有私有方法(ES2022+ 才有)
  3. 兼容性:旧浏览器需转译
  4. 某些特性限制
class MyClass {
  constructor() {
    this.method = () => { /* 箭头函数,this 绑定 */ };
  }
  // 不能这样写:
  // property = () => { };  // ES7+ 才支持
}

7. 转译结果对比

Babel 转译 ES6 类继承

// ES6
class Parent {
  constructor(name) { this.name = name; }
  say() { console.log(this.name); }
}

class Child extends Parent {
  constructor(name, age) {
    super(name);
    this.age = age;
  }
}

// Babel 转译后
"use strict";

function _inherits(subClass, superClass) {
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: { value: subClass, writable: true, configurable: true }
  });
  Object.setPrototypeOf(subClass, superClass);
}

function _createSuper(Derived) {
  return function _createSuperInternal() {
    var Super = _getPrototypeOf(Derived);
    var result = Reflect.construct(Super, arguments, _getPrototypeOf(this).constructor);
    return _possibleConstructorReturn(this, result);
  };
}

var Parent = function Parent(name) {
  this.name = name;
};

Parent.prototype.say = function say() {
  console.log(this.name);
};

var Child = /*#__PURE__*/function (_Parent) {
  _inherits(Child, _Parent);
  var _super = _createSuper(Child);
  
  function Child(name, age) {
    var _this;
    _this = _super.call(this, name);
    _this.age = age;
    return _this;
  }
  return Child;
}(Parent);

8. 实际应用建议

适合使用寄生组合继承的场景

// 1. 需要支持 IE 的项目
if (typeof window !== 'undefined' && 
    /MSIE|Trident/.test(window.navigator.userAgent)) {
  // 使用寄生组合继承
  function IECompatibleClass() {}
}

// 2. 需要深度定制继承逻辑
function createCustomInheritance(SuperClass, mixins) {
  function CustomClass() {
    SuperClass.apply(this, arguments);
    mixins.forEach(mixin => {
      mixin.init && mixin.init.apply(this, arguments);
    });
  }
  
  CustomClass.prototype = Object.create(SuperClass.prototype);
  mixins.forEach(mixin => {
    Object.assign(CustomClass.prototype, mixin.methods);
  });
  
  return CustomClass;
}

适合使用 ES6 类继承的场景

// 1. 现代前端框架
import React from 'react';

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  render() {
    return <div>{this.state.count}</div>;
  }
}

// 2. Node.js 服务端
class DatabaseService {
  constructor(config) {
    this.config = config;
  }
  
  async connect() { /* ... */ }
}

class MySQLService extends DatabaseService {
  async query(sql) { /* ... */ }
}

// 3. 需要类型检查
class Person {
  constructor(name) {
    if (typeof name !== 'string') {
      throw new Error('Name must be a string');
    }
    this.name = name;
  }
}

9. 混合使用模式(一般用不到)

// 结合两种方式
// 使用类作为基础
class EventEmitter {
  constructor() {
    this.events = {};
  }
  
  on(event, listener) {
    (this.events[event] || (this.events[event] = [])).push(listener);
  }
  
  emit(event, ...args) {
    (this.events[event] || []).forEach(listener => listener(...args));
  }
}

// 使用寄生组合继承扩展
function createObservable(SuperClass) {
  function Observable() {
    SuperClass.apply(this, arguments);
    this.observers = [];
  }
  
  Observable.prototype = Object.create(SuperClass.prototype);
  Observable.prototype.constructor = Observable;
  
  Observable.prototype.subscribe = function(observer) {
    this.observers.push(observer);
  };
  
  Observable.prototype.notify = function(data) {
    this.observers.forEach(observer => observer(data));
  };
  
  return Observable;
}

// 组合使用
class Model extends createObservable(EventEmitter) {
  constructor(data) {
    super();
    this.data = data;
  }
  
  set(key, value) {
    this.data[key] = value;
    this.notify({ key, value });
    this.emit('change', { key, value });
  }
}

10. 最佳实践

现代项目

// 使用 ES6 类继承
class BaseService {
  constructor(config) {
    this.config = config;
  }
  
  validateConfig() {
    // 验证配置
  }
  
  async init() {
    throw new Error('Method not implemented');
  }
}

class ApiService extends BaseService {
  constructor(config) {
    super(config);
    this.validateConfig();
  }
  
  async init() {
    // 实现初始化
  }
}

需要兼容性的项目

// 使用模块化的寄生组合继承
function createClass(superClass, definition) {
  const Child = definition.constructor || function() {
    superClass.apply(this, arguments);
  };
  
  // 原型继承
  Child.prototype = Object.create(superClass.prototype);
  Child.prototype.constructor = Child;
  
  // 混入方法
  if (definition.methods) {
    Object.assign(Child.prototype, definition.methods);
  }
  
  // 静态方法继承
  Object.setPrototypeOf(Child, superClass);
  
  // 添加静态方法
  if (definition.statics) {
    Object.assign(Child, definition.statics);
  }
  
  return Child;
}

总结

方面 推荐选择 原因
现代 Web 项目 ES6 类继承 语法简洁,工具支持好
库/框架开发 ES6 类继承 更好的开发体验
需要支持旧 IE 寄生组合继承 兼容性好
性能关键 ES6 类继承 现代引擎优化好
代码可读性 ES6 类继承 语法更清晰
团队熟悉度 团队熟悉的 降低学习成本

最终建议

  • 新项目一律使用 ES6 类继承
  • 旧项目维护时,如果已经是寄生组合继承,可保持
  • 需要特殊继承模式时,可混用两种方式
  • 始终考虑团队成员技能和项目需求

自造微前端

作者 ggbond
2025年12月9日 16:32

起因是,我要在一个大型后台管理应用中剥离出一个管理模块,这样做的好处当然是可以单独开发和发布,但是坏处也有,比如部分全局变量污染,UI不一致,状态通信传导等。虽然市面上已经有很多微前端方案了,比如qiankun等,但是这都需要对这个大仓库应用进行大动工,对原应用造成一些负载和可能意想不到的错误,非我所愿也,所以在这里我打算自己搞个简化的方案。 原应用是react+dva,

基座应用改造

我们设计的主应用核心是;

  1. 主应用不直接打包子应用的代码,而是运行时按需加载子应用的 JS 资源。
  2. 将子应用的路由注册到主应用的路由系统中,实现无缝跳转。
  3. 通过全局变量(window)共享库,避免重复加载,也可形成单例。 我们先改造子应用路由代码,为了解决刚才说的UI和一些重要库不一致问题,我们将直接暴露库到Window中,有:
  4. antd
  5. React主库 另外状态通信的问题,我们直接使用dva去修饰取得的子应用Com,并把部分方法如connect暴露到window中。

代码如下

import React from 'react';
import {
  Alert,
  Drawer,
} from 'antd';
import { connect } from 'dva';

window.React = React;
window.CONNECT = connect;
window.Antd_ZI = {
  Drawer,
  Alert,
};

const configUrl = {
  test: 'https://zi.test.cn',
  product: 'https://zi.prod.com',
};

const env = 'product';

const mapStateToProps = (state) => {
  const currentState = state.adManage;
  const { userAuth, userInfo } = state.app;
  return { ...currentState, userAuth, userInfo };
};

const insertScript = (e, isStyle = false) => {
  return new Promise((reslove, reject) => {
    const i = isStyle ? document.createElement('link') : document.createElement('script');
    isStyle || i.setAttribute('type', 'text/javascript');
    isStyle || i.setAttribute('src', e);
    isStyle && i.setAttribute('href', e);
    isStyle && i.setAttribute('rel', 'stylesheet');
    isStyle && i.setAttribute('type', 'text/css');
    function onload() {
      if (!(this.readyState && this.readyState !== 'loaded' && this.readyState !== 'complete')) {
        i.onload = null;
        i.onreadystatechange = i.onload;
        reslove();
      }
    }
    function onerror(ee) {
      reject(ee);
    }
    i.onreadystatechange = onload;
    i.onload = onload;
    i.onerror = onerror;
    document.querySelector('head').appendChild(i);
  });
};

function ERROR() {
  return '加载静态资源失败,请稍后重试';
}

const subappRoutes = [];

const AyncComponent = async (pathname) => {
  const id = pathname;
  // 子工程资源是否加载完成
  let ayncLoaded = false;
  if (subappRoutes[id]) {
    // 如果已经加载过该子工程的模块,则不再加载,直接取缓存的routes
    ayncLoaded = true;
  } else if (window.ziPLATFORMSLOT && window.ziPLATFORMSLOT[pathname]) {
    const res = await window.ziPLATFORMSLOT[pathname]();
    subappRoutes[id] = res.default;
    ayncLoaded = true;
    // return subappRoutes[id];
  } else {
    try {
      await insertScript(`${configUrl[env]}/js/index.js?_=${new Date().getTime()}`);
      if (window.ziPLATFORMSLOT && window.ziPLATFORMSLOT[pathname]) {
        const res = await window.ziPLATFORMSLOT[pathname]();
        subappRoutes[id] = res.default;
        ayncLoaded = true;
      }
    } catch (error) {
      console.log('加载js失败', error);
    }
  }
  return ayncLoaded ? connect(mapStateToProps)(subappRoutes[id]) : ERROR;
};

export default [
  {
    name: '资源管理',
    path: 'ziMedia',
    component: () => AyncComponent('ziMedia'),
  },
  {
    name: '黑名单管理',
    path: 'ziBlack',
    component: () => AyncComponent('ziBlack'),
  },
];

子应用改造

子应用中,入口文件我们需要定义publicPath保证资源的正确引用

const configUrl = {
  test: 'https://zi.test.cn',
  product: 'https://zi.prod.com',
};

const env = 'product';
// webpack 的魔法变量,用于指定异步加载的 JS/CSS 文件的基地址(如 `import()` 动态导入的模块)
__webpack_public_path__ = `${configUrl[env]}/js/`;

const routes = {
  version: '1.0.0',
  ziMedia: () => import('./ziMedia/index'),
  ziBlack: () => import('./ziBlack/index'),
};

export default routes;

在子应用的页面中,我们基本不需要做改造,但是在webpack的配置中,我们在base中需要:

{
    ...
    output:{
        ...
          libraryExport: 'default',
          library: 'ziPLATFORMSLOT',
          libraryTarget: 'umd',
          umdNamedDefine: true,
    },
    externals: {
      'react': 'React',
      'antd': 'Antd_ZI',
      // 'react-dom':'ReactDOM'
    }
}

在prod配置中,为了保证子js能正确加载执行,我们使用InlineChunkHtmlPlugin插件,避免微前端环境下因路径问题导致运行时脚本加载失败。 至此,完成。

script 标签的异步加载:async、defer、type="module" 详解

作者 FarmerLiusun
2025年12月9日 16:02

script 标签的异步加载:async、defer、type="module" 全面解析

本文主要是分享一下,关于加载script多种方式及区别

  • 1.同步加载:<script src="index.js"></script>
  • 2.defer 异步加载:<script defer src="index.js"></script>
  • 3.async 异步加载:<script async src="index.js"></script>
  • 4.module 异步加载:<script type="module" src="index.js"></script>
  • 5.module async 异步加载:<script type="module" src="index.js" async></script>

众农周知,浏览器解析html时,如果遇到使用 <script src="index.js"></script> 加载js时,html的解析会被阻塞,这个时候会先去下载index.js,等待下载完并执行完js之后,然后再继续解析html。这样的话,如果js下载时间过长,无疑会增加页面的渲染时间,那么,有没有啥办法可以让解析html与下载js并行执行呢?有的,并且提供了多种。

1.五种加载方式深度解析

1.1 同步加载 <script src="index.js"></script>

特点
  • 阻塞解析html
  • 须等加载完js并执行完之后才能继续解析html
  • 增加首屏渲染时间

不推荐使用

适用场景
  1. 传统的加载script方式,需要兼容低版本浏览器
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- 同步加载且阻塞解析html -->
   <script src="index.js"></script>
  </head>
  <body>
  </body>
</html>

1.2 异步加载,使用 defer属性

特点
  • 并行下载js
  • 不阻塞解析html,解析html与下载并行
  • 在html解析完成DOMContentLoaded事件触发执行
  • 多个script时,异步加载,按顺序执行
适用场景
  1. 加载的js存在依赖关系,须按顺序执行
  2. 加载的js需要操作DOM
  3. 常见vue、react项目中,构建工具已自动处理,编译后自动添加defer属性
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link rel="icon" href="/test/favicon.ico" />
    <title>xxxxx管理系统</title>
    
    <!-- 异步加载,优先执行第三方依赖 -->
    <script defer="defer" src="/test/js/chunk-vendors.0821bbb1.js"></script>
    <!-- 异步加载,上面的js执行完再执行 -->
    <script defer="defer" src="/test/js/app.566a7ad5.js"></script>
    
    <link href="/test/css/chunk-vendors.ff7db5f3.css" rel="stylesheet" />
    <link href="/test/css/app.0d1bd94e.css" rel="stylesheet" />
  </head>

  <body>
    <noscript
      ><strong
        >System doesn't work properly without JavaScript enabled. Please enable
        it to continue.</strong
      ></noscript
    >
    <div id="app"></div>
  </body>
</html>

1.3 异步加载,使用 async属性

特点
  • 并行下载js
  • 下载阶段,不阻塞解析html,解析html与下载并行
  • 下载完成后马上执行
  • 多个script时,异步加载,谁先下载完谁就执行
  • 可能阻塞解析html,如果解析html完成前就已经下载完了,那就会执行js,阻塞解析html
  • 可能会阻塞 DOMContentLoaded 事件的触发时机

    当js在 DOMContentLoaded 事件触发前完成下载并执行,就会延长 DOMContentLoaded的触发时间,js的执行会阻塞主线程,执行完才会触发 DOMContentLoaded事件

适用场景
  1. 加载的js没有赖关系,单独执行
  2. 加载的js无需操作DOM
  3. 常用于分析、广告、埋点脚本加载
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link rel="icon" href="/test/favicon.ico" />
    <title>xxxxxx官网</title>
    
    <!-- 异步加载,独立运行,无脚本依赖 -->
    <script async src="https://www.guanggao.com/gg/js?id=GG_1"></script>
    <script async src="https://www.guanggao.com/gg/js?id=GG_2"></script>
  </head>

  <body>
    <noscript
      ><strong
        >System doesn't work properly without JavaScript enabled. Please enable
        it to continue.</strong
      ></noscript
    >
    <div id="app"></div>
  </body>
</html>

1.4 异步加载,使用 type="module"属性

特点与defer差不多
  • 不阻塞解析html,解析html与下载并行
  • 在html解析完成DOMContentLoaded事件触发执行
  • 多个script时,异步加载,按顺序执行
  • defer主要差异是,使用esm模块化,且默认开启严格模式
适用场景 与defer差不多
  1. 加载的js存在依赖关系,须按顺序执行
  2. 加载的js需要操作DOM
  3. 使用esm开发的脚本
  4. 常见使用vite构建的vue、react项目中,编译后自动添加type="module"属性
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link rel="icon" href="/test/favicon.ico" />
    <title>xxxxxx系统</title>
    
    <!-- 异步加载,使用esm开发 -->
    <script type="module" crossorigin src="/assets/index-BmxZ5FsG.js"></script>
  </head>

  <body>
    <noscript
      ><strong
        >System doesn't work properly without JavaScript enabled. Please enable
        it to continue.</strong
      ></noscript
    >
    <div id="app"></div>
  </body>
</html>

1.5 异步加载,使用 type="module" async属性

特点与async差不多
  • 并行下载js
  • 下载阶段,不阻塞解析html,解析html与下载并行
  • 下载完成后马上执行
  • 多个script时,异步加载,谁先下载完谁就执行
  • 可能阻塞解析html,如果解析html完成前就已经下载完了,那就会执行js,阻塞解析html
  • 可能会阻塞 DOMContentLoaded 事件的触发时机

    当js在 DOMContentLoaded 事件触发前完成下载并执行,就会延长 DOMContentLoaded的触发时间,js的执行会阻塞主线程,执行完才会触发 DOMContentLoaded事件

  • async主要差异是,使用esm模块化,且默认开启严格模式``
适用场景 与async差不多
  1. 加载的js没有赖关系,单独执行
  2. 加载的js无需操作DOM
  3. 常用于分析、广告、埋点脚本加载
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link rel="icon" href="/test/favicon.ico" />
    <title>xxxxxx官网</title>
    
    <!-- 异步加载,使用esm开发,独立运行,无脚本依赖 -->
    <script type="module" async src="https://www.guanggao.com/gg/js?id=GG_1"></script>
  </head>

  <body>
    <noscript
      ><strong
        >System doesn't work properly without JavaScript enabled. Please enable
        it to continue.</strong
      ></noscript
    >
    <div id="app"></div>
  </body>
</html>

2.总结

2.1.加载script的5种方式:

  • 同步-阻塞解析html: <script>
  • 异步-不阻塞解析html-按顺序执行: <script defer>
  • 异步-不阻塞解析html-无序下载完立即执行: <script async>
  • 异步-esm-不阻塞解析html-按顺序执行: <script type="module">
  • 异步-esm-不阻塞解析html-无序下载完立即执行: <script type="module" async>

2.2.各方式差异:

加载方式 执行时机 是否阻塞HTML解析 执行顺序 适用场景
<script> 立即执行 文档顺序 传统脚本
<script defer> DOM解析后 文档顺序 依赖DOM的脚本
<script async> 下载完成后 下载不阻塞,执行可能阻塞 无序 独立第三方脚本
<script type="module"> DOM解析后 文档顺序 ES模块化代码
<script type="module" async> 下载完成后 下载不阻塞,执行可能阻塞 无序 独立ES模块

2.3.加载流程图:

image.png

2.4.使用建议:

  • js使用非esm,优先考虑使用 defer
  • js使用esm,优先考虑使用 type="module"

Gemini写应用(二)

作者 Miss妤
2025年12月9日 16:02

emmm 其实还没写(一),我会觉得(一)会是一个对于项目的描述,但我觉得对于server的准备更着急要记录下(万一关了控制台,我怕是找不到了)

这篇文章是我自己学习的过程,如有问题,请指教~

用Gemini在仓库中生成server

image.png

Trae中优化代码

可以看到以上代码pull进编辑器之后是无法运行的,可以输入指令,AI排查无法起服务的原因

image.png

经过排查:MongoDB没有安装,导致后端API调用失败。需要安装MongoDB并启动服务。

image.png

接下来就是安装 MongoDB的过程:

image.png

安装之后控制台运行:

npm install brew
brew install mongodb-atlas
atlas setup

To verify your account, copy your one-time verification code: xxxx-xxxx

访问:account.mongodb.com/account/reg… 创建、登陆mongoDB账号,将上面获得的code填入,关联成功

接下来访问页面:cloud.mongodb.com/v2/6937cf05… 就可以查看到自己的database了

❌
❌