HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

hbuilderx 启动 微信小程序 卡在 微信开发者工具 新建项目 页面里的看过来!

微信小程序 微信开发者工具 HBuilderX

1· 确定此小程序是你微信开发权限范围内
2· 直接下载hbuilderx 和 微信开发者工具 最新版本覆盖
运行就可以

1· 确定此小程序是你微信开发权限范围内
2· 直接下载hbuilderx 和 微信开发者工具 最新版本覆盖
运行就可以

电影《疯狂动物城2》迅雷BT(高清BD)下载[MP4/2.88GB/3.67B]无删减资源

动画

  《疯狂动物城2》:在理想与现实的碰撞中续写都市寓言
  2025年11月26日,暌违九年的《疯狂动物城2》以中美同步上映的姿态重返大银幕。这部现象级动画续作不仅延续了前作色彩斑斓的视觉奇观,更以更深刻的叙事内核直击现代都市人的精神困境。当朱迪与尼克在百年庆典的烟火中穿梭,当爬行动物与哺乳动物的矛盾撕裂城市表面,这部动画电影用童话的笔触,为成年人绘制了一幅关于理想、偏见与和解的都市寓言。
  《疯狂动物城2》下载:点击前往
  一、叙事升级:从偏见对抗到系统困境
  相较于前作聚焦食草与食肉动物的二元对立,《疯狂动物城2》将矛盾升级为哺乳族与爬行族的族群冲突。故事始于记载核心生态系统“气候墙”设计图的古籍失窃,朱迪与尼克因办案理念分歧被强制接受“伴侣心理辅导”。当他们追捕首次现身动物城的蛇盖瑞时,发现这个被污名化的爬行动物实为猞猁黑帮阴谋的牺牲品。三人组成“救赎联盟”深入沼泽市场,最终揭露了煽动族群对立的深层阴谋。
  这种叙事升级暗合着现实世界的复杂性。当朱迪在庆典现场目睹爬行动物群体被驱赶至城市边缘,当尼克发现所谓“秩序维护者”实为利益集团的工具,影片撕开了乌托邦城市的B面——那些被发展红利遗忘的群体,正在用沉默或暴烈的方式表达失落。导演通过蛇盖瑞的台词“我们不是慢,是被设计成慢”直指系统暴力,这种隐喻在现实中对应着技术迭代中掉队的劳动者、城市化进程中失地的原住民。
  二、角色重构:理想主义者的中年困境
  朱迪与尼克的角色弧光在续作中完成从“建立理想”到“维护理想”的蜕变。曾经的兔警官因过度责任感陷入焦虑,她会在追捕行动中反复检查装备清单,在心理咨询室里因尼克的一句“你根本不信我能做好”而崩溃。狐狸尼克则用玩世不恭掩饰对“被驯化”的抗拒,他会在执行任务时故意绕远路,用插科打诨掩盖对体制的失望。
  这种性格裂痕在“气候墙危机”中达到顶点。当城市核心系统濒临崩溃,朱迪坚持按程序申请支援,尼克却擅自破坏规则启动备用方案。两人在暴雨中的对峙戏堪称动画史经典:浑身湿透的朱迪举着通讯器嘶吼“程序错误可以修正,信任破碎无法弥补”,尼克抹去脸上的雨水冷笑“你所谓的程序,正在杀死这座城市”。这场争吵撕开了所有职场人的伤疤——当效率与温度冲突时,我们究竟该做坚守原则的守夜人,还是灵活变通的破壁者?
  三、情感叙事:成年人的暧昧经济学
  影片对朱迪与尼克关系的处理堪称精妙。前作中“我知道你爱我”的经典台词,在续作里升华为更复杂的情感博弈。当朱迪在心理辅导室说出“我最大的恐惧是变成自己讨厌的那种警察”,尼克下意识握住她的爪子;当尼克为保护朱迪被猞猁抓伤,朱迪背着他穿越沼泽时哼唱起《尝试一切》的变调版。这些细节构建起超越友情的情感张力,却始终克制于爱情线之外。
  这种暧昧经济学精准击中了都市成年人的情感困境。我们在职场中培养着“战友情谊”,在社交平台维系着“点赞之交”,却越来越难以定义亲密关系的边界。影片用朱迪的独白“有些关系不是突然破裂,而是一次次‘算了,懒得说了’的结果”道破真相。当两人在庆典烟火下重归于好,没有俗套的接吻镜头,只有尼克为朱迪调整歪掉的警徽,这个动作比任何誓言都更具说服力。
  四、视听革新:技术赋能的沉浸体验
  迪士尼此次在技术层面实现突破性升级。IMAX激光厅的放映中,朱迪跳跃时耳朵抖动的绒毛、尼克狡黠眼神里的反光、沼泽市场蒸腾的雾气都纤毫毕现。4DX影厅的动感座椅会随着飞车追逐产生颠簸感,当朱迪与尼克穿越雨林时,座椅两侧甚至会喷洒带有青草香气的水雾。
  音乐设计同样可圈可点。夏奇拉演唱的主题曲《Zoo》融合电子乐与拉丁节奏,在百年庆典场景中与前作《尝试一切》形成互文。当朱迪发现古籍失窃真相时,配乐突然切换为低沉的大提琴独奏,这种音画对位强化了叙事张力。值得关注的是,上海迪士尼乐园全球首发的《Zoo》夜间投影秀,用3D mapping技术将城堡变成流动的动物城,实现电影与主题公园的IP联动。
  五、社会隐喻:都市寓言的当代解码
  影片中多个场景构成精妙的社会隐喻。那间象征体制僵化的“伴侣心理咨询室”,墙上挂着“沟通是解决问题的第一步”的标语,却用隔音玻璃将咨询者与外界隔绝;百年庆典的宣传口号“动物城:永远向前”,与爬行动物群体举着的“我们也在前进”的标语形成残酷对照;最令人脊背发凉的细节是,猞猁黑帮通过控制媒体传播“爬行动物携带病毒”的谣言,这与现实中的信息战形成镜像。
  这些隐喻在首映礼后的观众讨论中持续发酵。有程序员在豆瓣短评中写道:“朱迪修复气候墙的程序漏洞,就像我们在深夜调试代码”;有社畜在微博感慨:“尼克对体制的反抗,是我每天想辞职却不敢的内心写照”;更有城市规划师在知乎分析:“动物城的交通系统设计,暴露了智慧城市建设的伦理困境”。
  结语:在童话中寻找现实解药
  当片尾字幕升起,朱迪与尼克并肩站在气候墙顶端俯瞰城市,这个镜头与前作结尾形成完美闭环。但续作的价值在于,它不再提供“所有动物都能和平共处”的简单答案,而是展现理想主义者如何在泥泞中继续前行。就像尼克说的:“真正的成熟不是学会避免冲突,而是学会处理冲突后的情绪残渣。”
  在这个算法推荐制造信息茧房、社交媒体加剧群体极化的时代,《疯狂动物城2》用童话的外壳包裹着尖锐的现实追问。它提醒我们:当城市以效率之名将个体异化为齿轮,当偏见以“政治正确”之名掩盖真实诉求,或许我们都需要像朱迪和尼克那样,在某个暴雨夜与伙伴坦诚相对,在情绪崩溃后重新握手。毕竟,维护理想的代价从来不是保持完美,而是承认脆弱后依然选择前行。

继续阅读 »

  《疯狂动物城2》:在理想与现实的碰撞中续写都市寓言
  2025年11月26日,暌违九年的《疯狂动物城2》以中美同步上映的姿态重返大银幕。这部现象级动画续作不仅延续了前作色彩斑斓的视觉奇观,更以更深刻的叙事内核直击现代都市人的精神困境。当朱迪与尼克在百年庆典的烟火中穿梭,当爬行动物与哺乳动物的矛盾撕裂城市表面,这部动画电影用童话的笔触,为成年人绘制了一幅关于理想、偏见与和解的都市寓言。
  《疯狂动物城2》下载:点击前往
  一、叙事升级:从偏见对抗到系统困境
  相较于前作聚焦食草与食肉动物的二元对立,《疯狂动物城2》将矛盾升级为哺乳族与爬行族的族群冲突。故事始于记载核心生态系统“气候墙”设计图的古籍失窃,朱迪与尼克因办案理念分歧被强制接受“伴侣心理辅导”。当他们追捕首次现身动物城的蛇盖瑞时,发现这个被污名化的爬行动物实为猞猁黑帮阴谋的牺牲品。三人组成“救赎联盟”深入沼泽市场,最终揭露了煽动族群对立的深层阴谋。
  这种叙事升级暗合着现实世界的复杂性。当朱迪在庆典现场目睹爬行动物群体被驱赶至城市边缘,当尼克发现所谓“秩序维护者”实为利益集团的工具,影片撕开了乌托邦城市的B面——那些被发展红利遗忘的群体,正在用沉默或暴烈的方式表达失落。导演通过蛇盖瑞的台词“我们不是慢,是被设计成慢”直指系统暴力,这种隐喻在现实中对应着技术迭代中掉队的劳动者、城市化进程中失地的原住民。
  二、角色重构:理想主义者的中年困境
  朱迪与尼克的角色弧光在续作中完成从“建立理想”到“维护理想”的蜕变。曾经的兔警官因过度责任感陷入焦虑,她会在追捕行动中反复检查装备清单,在心理咨询室里因尼克的一句“你根本不信我能做好”而崩溃。狐狸尼克则用玩世不恭掩饰对“被驯化”的抗拒,他会在执行任务时故意绕远路,用插科打诨掩盖对体制的失望。
  这种性格裂痕在“气候墙危机”中达到顶点。当城市核心系统濒临崩溃,朱迪坚持按程序申请支援,尼克却擅自破坏规则启动备用方案。两人在暴雨中的对峙戏堪称动画史经典:浑身湿透的朱迪举着通讯器嘶吼“程序错误可以修正,信任破碎无法弥补”,尼克抹去脸上的雨水冷笑“你所谓的程序,正在杀死这座城市”。这场争吵撕开了所有职场人的伤疤——当效率与温度冲突时,我们究竟该做坚守原则的守夜人,还是灵活变通的破壁者?
  三、情感叙事:成年人的暧昧经济学
  影片对朱迪与尼克关系的处理堪称精妙。前作中“我知道你爱我”的经典台词,在续作里升华为更复杂的情感博弈。当朱迪在心理辅导室说出“我最大的恐惧是变成自己讨厌的那种警察”,尼克下意识握住她的爪子;当尼克为保护朱迪被猞猁抓伤,朱迪背着他穿越沼泽时哼唱起《尝试一切》的变调版。这些细节构建起超越友情的情感张力,却始终克制于爱情线之外。
  这种暧昧经济学精准击中了都市成年人的情感困境。我们在职场中培养着“战友情谊”,在社交平台维系着“点赞之交”,却越来越难以定义亲密关系的边界。影片用朱迪的独白“有些关系不是突然破裂,而是一次次‘算了,懒得说了’的结果”道破真相。当两人在庆典烟火下重归于好,没有俗套的接吻镜头,只有尼克为朱迪调整歪掉的警徽,这个动作比任何誓言都更具说服力。
  四、视听革新:技术赋能的沉浸体验
  迪士尼此次在技术层面实现突破性升级。IMAX激光厅的放映中,朱迪跳跃时耳朵抖动的绒毛、尼克狡黠眼神里的反光、沼泽市场蒸腾的雾气都纤毫毕现。4DX影厅的动感座椅会随着飞车追逐产生颠簸感,当朱迪与尼克穿越雨林时,座椅两侧甚至会喷洒带有青草香气的水雾。
  音乐设计同样可圈可点。夏奇拉演唱的主题曲《Zoo》融合电子乐与拉丁节奏,在百年庆典场景中与前作《尝试一切》形成互文。当朱迪发现古籍失窃真相时,配乐突然切换为低沉的大提琴独奏,这种音画对位强化了叙事张力。值得关注的是,上海迪士尼乐园全球首发的《Zoo》夜间投影秀,用3D mapping技术将城堡变成流动的动物城,实现电影与主题公园的IP联动。
  五、社会隐喻:都市寓言的当代解码
  影片中多个场景构成精妙的社会隐喻。那间象征体制僵化的“伴侣心理咨询室”,墙上挂着“沟通是解决问题的第一步”的标语,却用隔音玻璃将咨询者与外界隔绝;百年庆典的宣传口号“动物城:永远向前”,与爬行动物群体举着的“我们也在前进”的标语形成残酷对照;最令人脊背发凉的细节是,猞猁黑帮通过控制媒体传播“爬行动物携带病毒”的谣言,这与现实中的信息战形成镜像。
  这些隐喻在首映礼后的观众讨论中持续发酵。有程序员在豆瓣短评中写道:“朱迪修复气候墙的程序漏洞,就像我们在深夜调试代码”;有社畜在微博感慨:“尼克对体制的反抗,是我每天想辞职却不敢的内心写照”;更有城市规划师在知乎分析:“动物城的交通系统设计,暴露了智慧城市建设的伦理困境”。
  结语:在童话中寻找现实解药
  当片尾字幕升起,朱迪与尼克并肩站在气候墙顶端俯瞰城市,这个镜头与前作结尾形成完美闭环。但续作的价值在于,它不再提供“所有动物都能和平共处”的简单答案,而是展现理想主义者如何在泥泞中继续前行。就像尼克说的:“真正的成熟不是学会避免冲突,而是学会处理冲突后的情绪残渣。”
  在这个算法推荐制造信息茧房、社交媒体加剧群体极化的时代,《疯狂动物城2》用童话的外壳包裹着尖锐的现实追问。它提醒我们:当城市以效率之名将个体异化为齿轮,当偏见以“政治正确”之名掩盖真实诉求,或许我们都需要像朱迪和尼克那样,在某个暴雨夜与伙伴坦诚相对,在情绪崩溃后重新握手。毕竟,维护理想的代价从来不是保持完美,而是承认脆弱后依然选择前行。

收起阅读 »

电影《疯狂动物城2》迅雷BT完整下载[HD-1280P/3.23GBMKV中字]4k百度云资源

分享

  《疯狂动物城2:在多元共生中寻找城市秩序的密码》
  当朱迪·霍普斯警官举着电磁脉冲武器冲进雨林,尼克·王尔德的西装暗纹在激光中泛着冷光,新角色蛇盖瑞的鳞片折射出沼泽市场的霓虹——2025年11月26日上映的《疯狂动物城2》,以一场跨越哺乳动物与爬行动物的冒险,将都市寓言推向更深刻的维度。这部暌违九年的续作,不仅延续了前作对偏见的解构,更以“城市温度”为切口,撕开了现代文明高速发展背后的集体焦虑。
  《疯狂动物城2》下载:点击前往
  一、叙事升级:从“消除偏见”到“重构共生”
  前作《疯狂动物城》以食草动物与食肉动物的二元对立为叙事支点,用“野蛮化”危机叩击种族平等的命题。续作则将矛盾升级为哺乳动物与爬行动物的族群冲突:记载气候墙原始设计图的古籍失窃,蛇盖瑞因被诬陷成为逃犯,朱迪与尼克在追捕过程中发现猞猁黑帮煽动对立的阴谋。这一设定直指现实中的“外来者困境”——当城市以效率为名加速扩张,那些被视为“缓慢”“冷血”的群体,是否注定成为被边缘化的代价?
  影片核心场景“沼泽市场”的构建极具象征意义。这个由爬行动物主导的地下世界,既有潮湿闷热的生存法则,又保留着独特的文化密码:蛇盖瑞用鳞片摩擦发出求救信号,蜥蜴摊贩用尾巴卷起交易货币,鳄鱼保安在泥潭中潜伏。当朱迪与尼克以“救赎联盟”身份潜入时,他们不得不摘下象征体制权威的警徽,学习用爬行动物的逻辑理解世界。这种文化碰撞的细节,比任何说教都更具冲击力——当朱迪因习惯直立行走而暴露行踪时,尼克调侃:“在沼泽里,低调才是生存之道。”
  二、角色弧光:在裂痕中生长的灵魂伴侣
  朱迪与尼克的关系进化是影片最动人的叙事线。这对曾以“不用说也懂”为默契的搭档,在续作中因办案理念分歧被迫接受心理辅导。导演拜伦·霍华德坦言:“当理想主义进入体制内,真正的考验才刚刚开始。”朱迪用过度责任感对抗失控焦虑,尼克以玩世不恭掩饰被驯化的恐惧,咨询室里的沉默与争执,精准击中了职场中处理复杂人际关系的成年人。
  这种“结构性疲惫”在“动物城百年庆典”危机中达到高潮。当猞猁黑帮煽动哺乳动物围攻气候墙,朱迪坚持按程序申请支援,尼克却擅自破坏控制系统启动应急方案。两人在暴雨中对峙的场景,将成年人的困境具象化:我们是否在追求“正确”的过程中,丢失了最珍贵的联结?最终,朱迪撕碎心理评估报告,尼克卸下西装领带,他们选择用最原始的方式信任彼此——朱迪跳上尼克后背,狐狸驮着兔子冲向火海。这个充满动物本能的姿势,恰恰是对“灵魂伴侣”最生动的诠释:明知不同,仍愿并肩。
  三、视听革新:技术赋能的沉浸式寓言
  影片在视觉呈现上实现了突破性升级。杜比影院版本中,气候墙的粒子特效以每秒120帧的流畅度展现,当朱迪穿越极地与沙漠的温差层时,观众能清晰感受到睫毛上的冰晶与汗水蒸发的热浪。4DX影厅的动感座椅则将追车戏推向新高度:尼克驾驶的改装沙地车碾过沼泽时,座椅会突然倾斜45度模拟侧翻,当车辆冲破水幕时,两侧喷出的水雾带着苔藓的腥气。这种多感官刺激,让“热力追踪”从银幕延伸至现实。
  音乐叙事同样充满巧思。夏奇拉演唱的主题曲《Zoo》融入哥伦比亚风笛与电子鼓点,歌词“我们用差异编织成网”呼应影片主题。更令人惊喜的是,上海迪士尼乐园将这首歌曲与前作《尝试一切》编排成夜间投影秀,当朱迪与尼克的剪影在城堡上共舞时,背景音乐突然切换成两人争吵的录音片段,随后渐变为和解时的轻笑——这种打破第四面墙的设计,让观众成为故事的一部分。
  四、现实映照:一堂给成年人的情绪整顿课
  影片最深刻的洞察,在于它撕开了都市生活的体面伪装。当朱迪在庆典现场看到慢吞吞的乌龟被推搡时,她突然意识到:自己何尝不是偏见施加者?那些被她视为“低效”的爬行动物,或许只是需要更多时间适应城市的节奏。这种自我反思,在现实中处处可见:我们抱怨地铁里的老人挡路,却忘记他们可能第一次使用扫码进站;我们指责外卖小哥超速,却忽略他们正在与系统算法赛跑。
  影片结尾的“双彩蛋”设计颇具深意:第一个彩蛋揭示气候墙古籍失窃案的幕后黑手竟是动物城档案局,他们为维护城市形象销毁历史证据;第二个彩蛋则展现朱迪与尼克在心理咨询室签订“冲突解决协议”,承诺“下次争吵时先数到十”。这两个看似矛盾的结局,恰恰构成了对现代社会的双重叩问:我们是否在追求秩序的过程中,扼杀了真实的生命力?而那些被视为“问题”的裂痕,是否正是光照进来的地方?
  结语:在动物城里,我们都是寻找秩序的旅人
  当片尾字幕升起,上海迪士尼乐园的“疯狂动物城:热力追踪”景点正上演着新角色蛇盖瑞的表演。孩子们为湿漉漉的爬行动物欢呼,大人们却在朱迪与尼克的拥抱中红了眼眶。这部动画电影最伟大的成就,或许在于它用最天真的方式,触碰了最沉重的命题:在这个日益分裂的世界里,我们是否还能像孩子一样,相信差异可以共生,裂痕能够愈合?
  正如影片中那句被反复引用的台词:“真正的城市,不是由玻璃与钢铁筑成,而是由理解与包容编织。”当朱迪与尼克驾驶着沙地车冲向晨曦时,他们背上的光芒,何尝不是每个都市人心中那团未灭的理想主义之火?

继续阅读 »

  《疯狂动物城2:在多元共生中寻找城市秩序的密码》
  当朱迪·霍普斯警官举着电磁脉冲武器冲进雨林,尼克·王尔德的西装暗纹在激光中泛着冷光,新角色蛇盖瑞的鳞片折射出沼泽市场的霓虹——2025年11月26日上映的《疯狂动物城2》,以一场跨越哺乳动物与爬行动物的冒险,将都市寓言推向更深刻的维度。这部暌违九年的续作,不仅延续了前作对偏见的解构,更以“城市温度”为切口,撕开了现代文明高速发展背后的集体焦虑。
  《疯狂动物城2》下载:点击前往
  一、叙事升级:从“消除偏见”到“重构共生”
  前作《疯狂动物城》以食草动物与食肉动物的二元对立为叙事支点,用“野蛮化”危机叩击种族平等的命题。续作则将矛盾升级为哺乳动物与爬行动物的族群冲突:记载气候墙原始设计图的古籍失窃,蛇盖瑞因被诬陷成为逃犯,朱迪与尼克在追捕过程中发现猞猁黑帮煽动对立的阴谋。这一设定直指现实中的“外来者困境”——当城市以效率为名加速扩张,那些被视为“缓慢”“冷血”的群体,是否注定成为被边缘化的代价?
  影片核心场景“沼泽市场”的构建极具象征意义。这个由爬行动物主导的地下世界,既有潮湿闷热的生存法则,又保留着独特的文化密码:蛇盖瑞用鳞片摩擦发出求救信号,蜥蜴摊贩用尾巴卷起交易货币,鳄鱼保安在泥潭中潜伏。当朱迪与尼克以“救赎联盟”身份潜入时,他们不得不摘下象征体制权威的警徽,学习用爬行动物的逻辑理解世界。这种文化碰撞的细节,比任何说教都更具冲击力——当朱迪因习惯直立行走而暴露行踪时,尼克调侃:“在沼泽里,低调才是生存之道。”
  二、角色弧光:在裂痕中生长的灵魂伴侣
  朱迪与尼克的关系进化是影片最动人的叙事线。这对曾以“不用说也懂”为默契的搭档,在续作中因办案理念分歧被迫接受心理辅导。导演拜伦·霍华德坦言:“当理想主义进入体制内,真正的考验才刚刚开始。”朱迪用过度责任感对抗失控焦虑,尼克以玩世不恭掩饰被驯化的恐惧,咨询室里的沉默与争执,精准击中了职场中处理复杂人际关系的成年人。
  这种“结构性疲惫”在“动物城百年庆典”危机中达到高潮。当猞猁黑帮煽动哺乳动物围攻气候墙,朱迪坚持按程序申请支援,尼克却擅自破坏控制系统启动应急方案。两人在暴雨中对峙的场景,将成年人的困境具象化:我们是否在追求“正确”的过程中,丢失了最珍贵的联结?最终,朱迪撕碎心理评估报告,尼克卸下西装领带,他们选择用最原始的方式信任彼此——朱迪跳上尼克后背,狐狸驮着兔子冲向火海。这个充满动物本能的姿势,恰恰是对“灵魂伴侣”最生动的诠释:明知不同,仍愿并肩。
  三、视听革新:技术赋能的沉浸式寓言
  影片在视觉呈现上实现了突破性升级。杜比影院版本中,气候墙的粒子特效以每秒120帧的流畅度展现,当朱迪穿越极地与沙漠的温差层时,观众能清晰感受到睫毛上的冰晶与汗水蒸发的热浪。4DX影厅的动感座椅则将追车戏推向新高度:尼克驾驶的改装沙地车碾过沼泽时,座椅会突然倾斜45度模拟侧翻,当车辆冲破水幕时,两侧喷出的水雾带着苔藓的腥气。这种多感官刺激,让“热力追踪”从银幕延伸至现实。
  音乐叙事同样充满巧思。夏奇拉演唱的主题曲《Zoo》融入哥伦比亚风笛与电子鼓点,歌词“我们用差异编织成网”呼应影片主题。更令人惊喜的是,上海迪士尼乐园将这首歌曲与前作《尝试一切》编排成夜间投影秀,当朱迪与尼克的剪影在城堡上共舞时,背景音乐突然切换成两人争吵的录音片段,随后渐变为和解时的轻笑——这种打破第四面墙的设计,让观众成为故事的一部分。
  四、现实映照:一堂给成年人的情绪整顿课
  影片最深刻的洞察,在于它撕开了都市生活的体面伪装。当朱迪在庆典现场看到慢吞吞的乌龟被推搡时,她突然意识到:自己何尝不是偏见施加者?那些被她视为“低效”的爬行动物,或许只是需要更多时间适应城市的节奏。这种自我反思,在现实中处处可见:我们抱怨地铁里的老人挡路,却忘记他们可能第一次使用扫码进站;我们指责外卖小哥超速,却忽略他们正在与系统算法赛跑。
  影片结尾的“双彩蛋”设计颇具深意:第一个彩蛋揭示气候墙古籍失窃案的幕后黑手竟是动物城档案局,他们为维护城市形象销毁历史证据;第二个彩蛋则展现朱迪与尼克在心理咨询室签订“冲突解决协议”,承诺“下次争吵时先数到十”。这两个看似矛盾的结局,恰恰构成了对现代社会的双重叩问:我们是否在追求秩序的过程中,扼杀了真实的生命力?而那些被视为“问题”的裂痕,是否正是光照进来的地方?
  结语:在动物城里,我们都是寻找秩序的旅人
  当片尾字幕升起,上海迪士尼乐园的“疯狂动物城:热力追踪”景点正上演着新角色蛇盖瑞的表演。孩子们为湿漉漉的爬行动物欢呼,大人们却在朱迪与尼克的拥抱中红了眼眶。这部动画电影最伟大的成就,或许在于它用最天真的方式,触碰了最沉重的命题:在这个日益分裂的世界里,我们是否还能像孩子一样,相信差异可以共生,裂痕能够愈合?
  正如影片中那句被反复引用的台词:“真正的城市,不是由玻璃与钢铁筑成,而是由理解与包容编织。”当朱迪与尼克驾驶着沙地车冲向晨曦时,他们背上的光芒,何尝不是每个都市人心中那团未灭的理想主义之火?

收起阅读 »

有企业证书的小伙伴可以建联一下吗?有几个问题请教,酬劳丰厚

外包 上架

公司有项目需要上传,绝对正规且合规,需要跟由企业证书的小伙伴咨询一下相关事宜,酬劳丰厚(不低于几个月工资)!!qq290783040 .不用留言,请直接加我。万分感谢本平台能够提供交流的空间。

公司有项目需要上传,绝对正规且合规,需要跟由企业证书的小伙伴咨询一下相关事宜,酬劳丰厚(不低于几个月工资)!!qq290783040 .不用留言,请直接加我。万分感谢本平台能够提供交流的空间。

寻找有企业证书的小伙伴咨询洽谈,酬劳丰厚!

App打包 iOS打包 uni_app 上架

公司有项目需要上传,绝对正规且合规,需要跟由企业证书的小伙伴咨询一下相关事宜,酬劳丰厚(不低于几个月工资)!!qq290783040 .不用留言,请直接加我。万分感谢本平台能够提供交流的空间。

公司有项目需要上传,绝对正规且合规,需要跟由企业证书的小伙伴咨询一下相关事宜,酬劳丰厚(不低于几个月工资)!!qq290783040 .不用留言,请直接加我。万分感谢本平台能够提供交流的空间。

安卓app中vue2版本vue文件套nvue开发地图,map的getScale方法无响应

this.$nextTick(()=>{
console.log('---scale----',this.$map.getScale)
this.$map.getScale({
success(res){
console.log('---scale----',res.scale)
that.mapScale=scale;
},
fail(err){
console.log('===',err)
},
complete(res){
console.log('==complete=',res)
}
})
});

继续阅读 »

this.$nextTick(()=>{
console.log('---scale----',this.$map.getScale)
this.$map.getScale({
success(res){
console.log('---scale----',res.scale)
that.mapScale=scale;
},
fail(err){
console.log('===',err)
},
complete(res){
console.log('==complete=',res)
}
})
});

收起阅读 »

【ios手机连接不上】使用爱思助手连接手机

真机运行 iOS iOS真机运行失败

如题,使用爱思助手连接手机!

目前我这边使用iTunes没反应,换了爱思助手连接就好了,可能爱思助手做了啥处理吧

如题,使用爱思助手连接手机!

目前我这边使用iTunes没反应,换了爱思助手连接就好了,可能爱思助手做了啥处理吧

uniapp.dcloud 插件市场 提现过期问题

插件市场 提现

钱包里的钱还能过期 ,就发个通知, 不满100又不能提现 ,也没有反馈渠道

钱包里的钱还能过期 ,就发个通知, 不满100又不能提现 ,也没有反馈渠道

底部面板可独立窗口

折叠面板

希望可以独立

希望可以独立

JavaScript 函数式编程完全指南

偏应用 柯里化 函数式编程

JavaScript 函数式编程完全指南

📚 目录

  1. 什么是函数式编程
  2. 纯函数
  3. 不可变性
  4. 高阶函数
  5. 核心数组方法
  6. 函数组合与管道
  7. 柯里化与偏应用
  8. 实战案例

1. 什么是函数式编程

函数式编程(Functional Programming,FP)是一种编程范式,它将计算视为数学函数的求值,强调:

  • 纯函数:相同输入总是产生相同输出
  • 不可变性:数据一旦创建就不能被修改
  • 函数组合:通过组合简单函数构建复杂功能
  • 避免副作用:函数不应该改变外部状态
// ❌ 命令式编程(告诉计算机"怎么做")  
const numbers = [1, 2, 3, 4, 5];  
const doubled = [];  
for (let i = 0; i < numbers.length; i++) {  
    doubled.push(numbers[i] * 2);  
}  
console.log(doubled); // [2, 4, 6, 8, 10]  

// ✅ 函数式编程(告诉计算机"做什么")  
const numbersFP = [1, 2, 3, 4, 5];  
const doubledFP = numbersFP.map(n => n * 2);  
console.log(doubledFP); // [2, 4, 6, 8, 10]

2. 纯函数

2.1 纯函数的定义

纯函数满足两个条件:

  1. 确定性:相同的输入永远返回相同的输出
  2. 无副作用:不修改外部状态,不依赖外部可变状态
// ✅ 纯函数示例  
function add(a, b) {  
    return a + b;  
}  

function multiply(a, b) {  
    return a * b;  
}  

function greet(name) {  
    return `Hello, ${name}!`;  
}  

// 多次调用,结果永远相同  
console.log(add(2, 3));      // 5  
console.log(add(2, 3));      // 5  
console.log(multiply(4, 5)); // 20  
console.log(greet('Alice')); // "Hello, Alice!"

2.2 非纯函数示例

// ❌ 非纯函数:依赖外部变量  
let taxRate = 0.1;  
function calculateTax(amount) {  
    return amount * taxRate; // 依赖外部变量 taxRate  
}  

console.log(calculateTax(100)); // 10  
taxRate = 0.2;  
console.log(calculateTax(100)); // 20 - 相同输入,不同输出!  

// ❌ 非纯函数:修改外部状态  
let total = 0;  
function addToTotal(value) {  
    total += value; // 副作用:修改了外部变量  
    return total;  
}  

console.log(addToTotal(5));  // 5  
console.log(addToTotal(5));  // 10 - 相同输入,不同输出!  

// ❌ 非纯函数:修改输入参数  
function addItem(cart, item) {  
    cart.push(item); // 副作用:修改了传入的数组  
    return cart;  
}  

const myCart = ['apple'];  
addItem(myCart, 'banana');  
console.log(myCart); // ['apple', 'banana'] - 原数组被修改了!

2.3 将非纯函数转换为纯函数

// ✅ 纯函数版本:将依赖作为参数传入  
function calculateTaxPure(amount, taxRate) {  
    return amount * taxRate;  
}  

console.log(calculateTaxPure(100, 0.1)); // 10  
console.log(calculateTaxPure(100, 0.2)); // 20  

// ✅ 纯函数版本:返回新值而不是修改外部状态  
function addToTotalPure(currentTotal, value) {  
    return currentTotal + value;  
}  

let totalPure = 0;  
totalPure = addToTotalPure(totalPure, 5);  
console.log(totalPure); // 5  

// ✅ 纯函数版本:返回新数组而不是修改原数组  
function addItemPure(cart, item) {  
    return [...cart, item]; // 返回新数组  
}  

const myCartPure = ['apple'];  
const newCart = addItemPure(myCartPure, 'banana');  
console.log(myCartPure); // ['apple'] - 原数组未被修改  
console.log(newCart);    // ['apple', 'banana']

2.4 纯函数的好处

// 1. 可测试性 - 纯函数非常容易测试  
function calculateDiscount(price, discountPercent) {  
    return price * (1 - discountPercent / 100);  
}  

// 测试用例  
console.log(calculateDiscount(100, 20) === 80);  // true  
console.log(calculateDiscount(50, 10) === 45);   // true  
console.log(calculateDiscount(200, 50) === 100); // true  

// 2. 可缓存性 - 相同输入总是相同输出,可以缓存结果  
function memoize(fn) {  
    const cache = new Map();  
    return function(...args) {  
        const key = JSON.stringify(args);  
        if (cache.has(key)) {  
            console.log('从缓存获取');  
            return cache.get(key);  
        }  
        console.log('计算中...');  
        const result = fn(...args);  
        cache.set(key, result);  
        return result;  
    };  
}  

function expensiveCalculation(n) {  
    // 模拟耗时计算  
    let result = 0;  
    for (let i = 0; i < n; i++) {  
        result += i;  
    }  
    return result;  
}  

const memoizedCalc = memoize(expensiveCalculation);  
console.log(memoizedCalc(10000)); // 计算中... 49995000  
console.log(memoizedCalc(10000)); // 从缓存获取 49995000  
console.log(memoizedCalc(5000));  // 计算中... 12497500

3. 不可变性

3.1 什么是不可变性

不可变性意味着数据一旦创建,就不能被修改。任何"修改"操作都会返回新的数据。

// ❌ 可变操作  
const person = { name: 'Alice', age: 25 };  
person.age = 26; // 直接修改原对象  
console.log(person); // { name: 'Alice', age: 26 }  

// ✅ 不可变操作  
const personImmutable = { name: 'Alice', age: 25 };  
const updatedPerson = { ...personImmutable, age: 26 }; // 创建新对象  
console.log(personImmutable); // { name: 'Alice', age: 25 } - 原对象不变  
console.log(updatedPerson);   // { name: 'Alice', age: 26 }

3.2 数组的不可变操作

const fruits = ['apple', 'banana', 'orange'];  

// ❌ 可变方法(会修改原数组)  
// push, pop, shift, unshift, splice, sort, reverse  

// ✅ 不可变方法(返回新数组)  
// map, filter, reduce, concat, slice, spread operator  

// 添加元素  
const withGrape = [...fruits, 'grape'];  
console.log(fruits);    // ['apple', 'banana', 'orange']  
console.log(withGrape); // ['apple', 'banana', 'orange', 'grape']  

// 在开头添加  
const withMango = ['mango', ...fruits];  
console.log(withMango); // ['mango', 'apple', 'banana', 'orange']  

// 删除元素(通过 filter)  
const withoutBanana = fruits.filter(f => f !== 'banana');  
console.log(withoutBanana); // ['apple', 'orange']  

// 修改元素(通过 map)  
const upperFruits = fruits.map(f => f.toUpperCase());  
console.log(upperFruits); // ['APPLE', 'BANANA', 'ORANGE']  

// 在指定位置插入  
const insertAt = (arr, index, item) => [  
    ...arr.slice(0, index),  
    item,  
    ...arr.slice(index)  
];  
console.log(insertAt(fruits, 1, 'kiwi')); // ['apple', 'kiwi', 'banana', 'orange']  

// 删除指定位置的元素  
const removeAt = (arr, index) => [  
    ...arr.slice(0, index),  
    ...arr.slice(index + 1)  
];  
console.log(removeAt(fruits, 1)); // ['apple', 'orange']  

// 更新指定位置的元素  
const updateAt = (arr, index, newValue) =>   
    arr.map((item, i) => i === index ? newValue : item);  
console.log(updateAt(fruits, 1, 'blueberry')); // ['apple', 'blueberry', 'orange']

3.3 对象的不可变操作

const user = {  
    name: 'Alice',  
    age: 25,  
    address: {  
        city: 'Beijing',  
        country: 'China'  
    },  
    hobbies: ['reading', 'coding']  
};  

// 更新顶层属性  
const userWithNewAge = { ...user, age: 26 };  
console.log(user.age);            // 25  
console.log(userWithNewAge.age);  // 26  

// 添加新属性  
const userWithEmail = { ...user, email: 'alice@example.com' };  
console.log(userWithEmail);  

// 删除属性  
const { age, ...userWithoutAge } = user;  
console.log(userWithoutAge); // { name: 'Alice', address: {...}, hobbies: [...] }  

// 更新嵌套属性(需要深层展开)  
const userWithNewCity = {  
    ...user,  
    address: {  
        ...user.address,  
        city: 'Shanghai'  
    }  
};  
console.log(user.address.city);           // 'Beijing'  
console.log(userWithNewCity.address.city); // 'Shanghai'  

// 更新数组属性  
const userWithNewHobby = {  
    ...user,  
    hobbies: [...user.hobbies, 'gaming']  
};  
console.log(user.hobbies);           // ['reading', 'coding']  
console.log(userWithNewHobby.hobbies); // ['reading', 'coding', 'gaming']

3.4 深度不可变更新工具函数

// 通用的深度更新函数  
function updatePath(obj, path, value) {  
    const keys = path.split('.');  

    if (keys.length === 1) {  
        return { ...obj, [keys[0]]: value };  
    }  

    const [first, ...rest] = keys;  
    return {  
        ...obj,  
        [first]: updatePath(obj[first], rest.join('.'), value)  
    };  
}  

const state = {  
    user: {  
        profile: {  
            name: 'Alice',  
            settings: {  
                theme: 'dark',  
                language: 'en'  
            }  
        }  
    }  
};  

const newState = updatePath(state, 'user.profile.settings.theme', 'light');  
console.log(state.user.profile.settings.theme);    // 'dark'  
console.log(newState.user.profile.settings.theme); // 'light'  

// 使用 Object.freeze 强制不可变(浅层)  
const frozenObj = Object.freeze({ a: 1, b: 2 });  
// frozenObj.a = 100; // 严格模式下会报错,非严格模式静默失败  
console.log(frozenObj.a); // 1  

// 深度冻结  
function deepFreeze(obj) {  
    Object.keys(obj).forEach(key => {  
        if (typeof obj[key] === 'object' && obj[key] !== null) {  
            deepFreeze(obj[key]);  
        }  
    });  
    return Object.freeze(obj);  
}  

const deepFrozenObj = deepFreeze({  
    a: 1,  
    b: { c: 2, d: { e: 3 } }  
});

4. 高阶函数

4.1 什么是高阶函数

高阶函数是至少满足以下条件之一的函数:

  1. 接受函数作为参数
  2. 返回一个函数
// 接受函数作为参数  
function executeOperation(a, b, operation) {  
    return operation(a, b);  
}  

const add = (x, y) => x + y;  
const multiply = (x, y) => x * y;  
const subtract = (x, y) => x - y;  

console.log(executeOperation(5, 3, add));      // 8  
console.log(executeOperation(5, 3, multiply)); // 15  
console.log(executeOperation(5, 3, subtract)); // 2  

// 返回一个函数  
function createMultiplier(factor) {  
    return function(number) {  
        return number * factor;  
    };  
}  

const double = createMultiplier(2);  
const triple = createMultiplier(3);  
const tenTimes = createMultiplier(10);  

console.log(double(5));   // 10  
console.log(triple(5));   // 15  
console.log(tenTimes(5)); // 50

4.2 常用高阶函数模式

// 1. 函数包装器 - 添加额外功能  
function withLogging(fn) {  
    return function(...args) {  
        console.log(`调用函数,参数: ${JSON.stringify(args)}`);  
        const result = fn(...args);  
        console.log(`返回结果: ${result}`);  
        return result;  
    };  
}  

function addNumbers(a, b) {  
    return a + b;  
}  

const addWithLogging = withLogging(addNumbers);  
addWithLogging(3, 4);  
// 输出:  
// 调用函数,参数: [3,4]  
// 返回结果: 7  

// 2. 函数计时器  
function withTiming(fn) {  
    return function(...args) {  
        const start = performance.now();  
        const result = fn(...args);  
        const end = performance.now();  
        console.log(`执行时间: ${(end - start).toFixed(2)}ms`);  
        return result;  
    };  
}  

function slowFunction() {  
    let sum = 0;  
    for (let i = 0; i < 1000000; i++) {  
        sum += i;  
    }  
    return sum;  
}  

const timedSlowFunction = withTiming(slowFunction);  
timedSlowFunction(); // 执行时间: x.xxms  

// 3. 只执行一次的函数  
function once(fn) {  
    let called = false;  
    let result;  

    return function(...args) {  
        if (!called) {  
            called = true;  
            result = fn(...args);  
        }  
        return result;  
    };  
}  

const initialize = once(() => {  
    console.log('初始化中...');  
    return { initialized: true };  
});  

console.log(initialize()); // 初始化中... { initialized: true }  
console.log(initialize()); // { initialized: true } - 不再打印"初始化中..."  
console.log(initialize()); // { initialized: true }  

// 4. 防抖函数  
function debounce(fn, delay) {  
    let timeoutId;  

    return function(...args) {  
        clearTimeout(timeoutId);  
        timeoutId = setTimeout(() => {  
            fn.apply(this, args);  
        }, delay);  
    };  
}  

const debouncedSearch = debounce((query) => {  
    console.log(`搜索: ${query}`);  
}, 300);  

// 快速连续调用,只有最后一次会执行  
debouncedSearch('a');  
debouncedSearch('ab');  
debouncedSearch('abc');  
// 300ms 后只输出: 搜索: abc  

// 5. 节流函数  
function throttle(fn, limit) {  
    let inThrottle = false;  

    return function(...args) {  
        if (!inThrottle) {  
            fn.apply(this, args);  
            inThrottle = true;  
            setTimeout(() => {  
                inThrottle = false;  
            }, limit);  
        }  
    };  
}  

const throttledScroll = throttle(() => {  
    console.log('处理滚动事件');  
}, 100);

4.3 创建专用函数

// 使用高阶函数创建专用函数  
function createValidator(validationFn, errorMessage) {  
    return function(value) {  
        if (validationFn(value)) {  
            return { valid: true, value };  
        }  
        return { valid: false, error: errorMessage };  
    };  
}  

const isNotEmpty = createValidator(  
    value => value && value.trim().length > 0,  
    '值不能为空'  
);  

const isEmail = createValidator(  
    value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),  
    '请输入有效的邮箱地址'  
);  

const isMinLength = (min) => createValidator(  
    value => value && value.length >= min,  
    `长度至少为 ${min} 个字符`  
);  

console.log(isNotEmpty('hello'));     // { valid: true, value: 'hello' }  
console.log(isNotEmpty(''));          // { valid: false, error: '值不能为空' }  
console.log(isEmail('test@mail.com')); // { valid: true, value: 'test@mail.com' }  
console.log(isEmail('invalid'));       // { valid: false, error: '请输入有效的邮箱地址' }  

const isMinLength5 = isMinLength(5);  
console.log(isMinLength5('hello'));    // { valid: true, value: 'hello' }  
console.log(isMinLength5('hi'));       // { valid: false, error: '长度至少为 5 个字符' }

5. 核心数组方法

5.1 map - 转换每个元素

// map 的基本用法  
const numbers = [1, 2, 3, 4, 5];  

// 简单转换  
const doubled = numbers.map(n => n * 2);  
console.log(doubled); // [2, 4, 6, 8, 10]  

const squared = numbers.map(n => n ** 2);  
console.log(squared); // [1, 4, 9, 16, 25]  

// 转换对象数组  
const users = [  
    { name: 'Alice', age: 25 },  
    { name: 'Bob', age: 30 },  
    { name: 'Charlie', age: 35 }  
];  

const names = users.map(user => user.name);  
console.log(names); // ['Alice', 'Bob', 'Charlie']  

const userCards = users.map(user => ({  
    ...user,  
    displayName: `${user.name} (${user.age}岁)`  
}));  
console.log(userCards);  
// [  
//   { name: 'Alice', age: 25, displayName: 'Alice (25岁)' },  
//   { name: 'Bob', age: 30, displayName: 'Bob (30岁)' },  
//   { name: 'Charlie', age: 35, displayName: 'Charlie (35岁)' }  
// ]  

// 使用索引参数  
const indexed = numbers.map((n, index) => `${index}: ${n}`);  
console.log(indexed); // ['0: 1', '1: 2', '2: 3', '3: 4', '4: 5']  

// 自己实现 map  
function myMap(arr, fn) {  
    const result = [];  
    for (let i = 0; i < arr.length; i++) {  
        result.push(fn(arr[i], i, arr));  
    }  
    return result;  
}  

console.log(myMap([1, 2, 3], x => x * 10)); // [10, 20, 30]

5.2 filter - 过滤元素

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];  

// 基本过滤  
const evens = numbers.filter(n => n % 2 === 0);  
console.log(evens); // [2, 4, 6, 8, 10]  

const odds = numbers.filter(n => n % 2 !== 0);  
console.log(odds); // [1, 3, 5, 7, 9]  

const greaterThan5 = numbers.filter(n => n > 5);  
console.log(greaterThan5); // [6, 7, 8, 9, 10]  

// 过滤对象数组  
const products = [  
    { name: 'iPhone', price: 999, inStock: true },  
    { name: 'iPad', price: 799, inStock: false },  
    { name: 'MacBook', price: 1299, inStock: true },  
    { name: 'AirPods', price: 199, inStock: true }  
];  

const inStockProducts = products.filter(p => p.inStock);  
console.log(inStockProducts);  
// [  
//   { name: 'iPhone', price: 999, inStock: true },  
//   { name: 'MacBook', price: 1299, inStock: true },  
//   { name: 'AirPods', price: 199, inStock: true }  
// ]  

const affordableProducts = products.filter(p => p.price < 1000);  
console.log(affordableProducts);  
// [  
//   { name: 'iPhone', price: 999, inStock: true },  
//   { name: 'iPad', price: 799, inStock: false },  
//   { name: 'AirPods', price: 199, inStock: true }  
// ]  

// 组合条件  
const affordableInStock = products.filter(p => p.price < 1000 && p.inStock);  
console.log(affordableInStock);  
// [  
//   { name: 'iPhone', price: 999, inStock: true },  
//   { name: 'AirPods', price: 199, inStock: true }  
// ]  

// 去除假值  
const mixedArray = [0, 1, '', 'hello', null, undefined, false, true, NaN];  
const truthyValues = mixedArray.filter(Boolean);  
console.log(truthyValues); // [1, 'hello', true]  

// 去重  
const duplicates = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4];  
const unique = duplicates.filter((item, index, arr) => arr.indexOf(item) === index);  
console.log(unique); // [1, 2, 3, 4]  

// 自己实现 filter  
function myFilter(arr, predicate) {  
    const result = [];  
    for (let i = 0; i < arr.length; i++) {  
        if (predicate(arr[i], i, arr)) {  
            result.push(arr[i]);  
        }  
    }  
    return result;  
}  

console.log(myFilter([1, 2, 3, 4, 5], x => x > 2)); // [3, 4, 5]

5.3 reduce - 归约为单一值

// 基本用法:求和  
const numbers = [1, 2, 3, 4, 5];  

const sum = numbers.reduce((accumulator, current) => {  
    console.log(`accumulator: ${accumulator}, current: ${current}`);  
    return accumulator + current;  
}, 0);  
// accumulator: 0, current: 1  
// accumulator: 1, current: 2  
// accumulator: 3, current: 3  
// accumulator: 6, current: 4  
// accumulator: 10, current: 5  
console.log(sum); // 15  

// 求乘积  
const product = numbers.reduce((acc, cur) => acc * cur, 1);  
console.log(product); // 120  

// 找最大值  
const max = numbers.reduce((acc, cur) => cur > acc ? cur : acc, -Infinity);  
console.log(max); // 5  

// 找最小值  
const min = numbers.reduce((acc, cur) => cur < acc ? cur : acc, Infinity);  
console.log(min); // 1  

// 数组转对象  
const users = [  
    { id: 1, name: 'Alice' },  
    { id: 2, name: 'Bob' },  
    { id: 3, name: 'Charlie' }  
];  

const userMap = users.reduce((acc, user) => {  
    acc[user.id] = user;  
    return acc;  
}, {});  
console.log(userMap);  
// {  
//   1: { id: 1, name: 'Alice' },  
//   2: { id: 2, name: 'Bob' },  
//   3: { id: 3, name: 'Charlie' }  
// }  

// 分组  
const people = [  
    { name: 'Alice', age: 25, city: 'Beijing' },  
    { name: 'Bob', age: 30, city: 'Shanghai' },  
    { name: 'Charlie', age: 25, city: 'Beijing' },  
    { name: 'David', age: 30, city: 'Beijing' }  
];  

const groupByAge = people.reduce((acc, person) => {  
    const key = person.age;  
    if (!acc[key]) {  
        acc[key] = [];  
    }  
    acc[key].push(person);  
    return acc;  
}, {});  
console.log(groupByAge);  
// {  
//   25: [{ name: 'Alice', ... }, { name: 'Charlie', ... }],  
//   30: [{ name: 'Bob', ... }, { name: 'David', ... }]  
// }  

// 通用分组函数  
function groupBy(arr, keyFn) {  
    return arr.reduce((acc, item) => {  
        const key = keyFn(item);  
        if (!acc[key]) {  
            acc[key] = [];  
        }  
        acc[key].push(item);  
        return acc;  
    }, {});  
}  

console.log(groupBy(people, p => p.city));  
// {  
//   Beijing: [Alice, Charlie, David],  
//   Shanghai: [Bob]  
// }  

// 统计出现次数  
const words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];  

const wordCount = words.reduce((acc, word) => {  
    acc[word] = (acc[word] || 0) + 1;  
    return acc;  
}, {});  
console.log(wordCount); // { apple: 3, banana: 2, orange: 1 }  

// 扁平化数组  
const nested = [[1, 2], [3, 4], [5, 6]];  
const flattened = nested.reduce((acc, arr) => [...acc, ...arr], []);  
console.log(flattened); // [1, 2, 3, 4, 5, 6]  

// 深度扁平化  
const deepNested = [[1, [2, 3]], [4, [5, [6, 7]]]];  

function flatten(arr) {  
    return arr.reduce((acc, item) => {  
        if (Array.isArray(item)) {  
            return [...acc, ...flatten(item)];  
        }  
        return [...acc, item];  
    }, []);  
}  

console.log(flatten(deepNested)); // [1, 2, 3, 4, 5, 6, 7]  

// 使用 reduce 实现 map  
function mapWithReduce(arr, fn) {  
    return arr.reduce((acc, item, index) => {  
        acc.push(fn(item, index, arr));  
        return acc;  
    }, []);  
}  

console.log(mapWithReduce([1, 2, 3], x => x * 2)); // [2, 4, 6]  

// 使用 reduce 实现 filter  
function filterWithReduce(arr, predicate) {  
    return arr.reduce((acc, item, index) => {  
        if (predicate(item, index, arr)) {  
            acc.push(item);  
        }  
        return acc;  
    }, []);  
}  

console.log(filterWithReduce([1, 2, 3, 4, 5], x => x > 2)); // [3, 4, 5]  

// 自己实现 reduce  
function myReduce(arr, reducer, initialValue) {  
    let accumulator = initialValue;  
    let startIndex = 0;  

    if (arguments.length < 3) {  
        accumulator = arr[0];  
        startIndex = 1;  
    }  

    for (let i = startIndex; i < arr.length; i++) {  
        accumulator = reducer(accumulator, arr[i], i, arr);  
    }  

    return accumulator;  
}  

console.log(myReduce([1, 2, 3, 4, 5], (a, b) => a + b, 0)); // 15

5.4 其他有用的数组方法

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];  

// find - 找到第一个满足条件的元素  
const firstEven = numbers.find(n => n % 2 === 0);  
console.log(firstEven); // 2  

const firstGreaterThan5 = numbers.find(n => n > 5);  
console.log(firstGreaterThan5); // 6  

// findIndex - 找到第一个满足条件的元素的索引  
const firstEvenIndex = numbers.findIndex(n => n % 2 === 0);  
console.log(firstEvenIndex); // 1  

// some - 检查是否至少有一个元素满足条件  
const hasEven = numbers.some(n => n % 2 === 0);  
console.log(hasEven); // true  

const hasNegative = numbers.some(n => n < 0);  
console.log(hasNegative); // false  

// every - 检查是否所有元素都满足条件  
const allPositive = numbers.every(n => n > 0);  
console.log(allPositive); // true  

const allEven = numbers.every(n => n % 2 === 0);  
console.log(allEven); // false  

// includes - 检查数组是否包含某个值  
console.log(numbers.includes(5));  // true  
console.log(numbers.includes(11)); // false  

// flat - 扁平化数组  
const nestedArray = [1, [2, 3], [4, [5, 6]]];  
console.log(nestedArray.flat());    // [1, 2, 3, 4, [5, 6]]  
console.log(nestedArray.flat(2));   // [1, 2, 3, 4, 5, 6]  
console.log(nestedArray.flat(Infinity)); // [1, 2, 3, 4, 5, 6]  

// flatMap - map + flat(1)  
const sentences = ['Hello World', 'Goodbye World'];  
const words = sentences.flatMap(s => s.split(' '));  
console.log(words); // ['Hello', 'World', 'Goodbye', 'World']  

// 实用示例:处理可能返回数组的映射  
const data = [1, 2, 3];  
const duplicated = data.flatMap(n => [n, n]);  
console.log(duplicated); // [1, 1, 2, 2, 3, 3]

5.5 方法链式调用

const orders = [  
    { id: 1, customer: 'Alice', items: ['apple', 'banana'], total: 25, status: 'completed' },  
    { id: 2, customer: 'Bob', items: ['orange'], total: 15, status: 'pending' },  
    { id: 3, customer: 'Charlie', items: ['apple', 'grape', 'melon'], total: 45, status: 'completed' },  
    { id: 4, customer: 'David', items: ['banana'], total: 10, status: 'cancelled' },  
    { id: 5, customer: 'Eve', items: ['apple', 'orange'], total: 30, status: 'completed' }  
];  

// 链式调用:筛选已完成订单,计算平均订单金额  
const averageCompletedOrderValue = orders  
    .filter(order => order.status === 'completed')  
    .map(order => order.total)  
    .reduce((sum, total, _, arr) => sum + total / arr.length, 0);  

console.log(averageCompletedOrderValue); // 33.33...  

// 获取所有完成订单的商品列表(去重)  
const completedOrderItems = orders  
    .filter(order => order.status === 'completed')  
    .flatMap(order => order.items)  
    .filter((item, index, arr) => arr.indexOf(item) === index);  

console.log(completedOrderItems); // ['apple', 'banana', 'grape', 'melon', 'orange']  

// 创建订单摘要  
const orderSummary = orders  
    .filter(order => order.status !== 'cancelled')  
    .map(order => ({  
        orderId: order.id,  
        customer: order.customer,  
        itemCount: order.items.length,  
        total: order.total  
    }))  
    .sort((a, b) => b.total - a.total);  

console.log(orderSummary);  
// [  
//   { orderId: 3, customer: 'Charlie', itemCount: 3, total: 45 },  
//   { orderId: 5, customer: 'Eve', itemCount: 2, total: 30 },  
//   { orderId: 1, customer: 'Alice', itemCount: 2, total: 25 },  
//   { orderId: 2, customer: 'Bob', itemCount: 1, total: 15 }  
// ]  

// 按状态分组统计  
const statusStats = orders.reduce((acc, order) => {  
    if (!acc[order.status]) {  
        acc[order.status] = { count: 0, totalValue: 0 };  
    }  
    acc[order.status].count++;  
    acc[order.status].totalValue += order.total;  
    return acc;  
}, {});  

console.log(statusStats);  
// {  
//   completed: { count: 3, totalValue: 100 },  
//   pending: { count: 1, totalValue: 15 },  
//   cancelled: { count: 1, totalValue: 10 }  
// }

6. 函数组合与管道

6.1 函数组合 (Compose)

函数组合是将多个函数合并成一个函数,从右到左执行。

// 基本概念  
// compose(f, g, h)(x) 等价于 f(g(h(x)))  

// 简单的两个函数组合  
const compose2 = (f, g) => x => f(g(x));  

const addOne = x => x + 1;  
const double = x => x * 2;  

const addOneThenDouble = compose2(double, addOne); // 先加1,再乘2  
console.log(addOneThenDouble(5)); // (5 + 1) * 2 = 12  

const doubleThenAddOne = compose2(addOne, double); // 先乘2,再加1  
console.log(doubleThenAddOne(5)); // (5 * 2) + 1 = 11  

// 通用的 compose 函数(支持多个函数)  
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);  

const square = x => x ** 2;  
const negate = x => -x;  

const composed = compose(negate, square, addOne, double);  
// 执行顺序: double -> addOne -> square -> negate  
// 5 -> 10 -> 11 -> 121 -> -121  
console.log(composed(5)); // -121  

// 更复杂的例子:处理字符串  
const trim = str => str.trim();  
const toLowerCase = str => str.toLowerCase();  
const split = delimiter => str => str.split(delimiter);  
const join = delimiter => arr => arr.join(delimiter);  
const map = fn => arr => arr.map(fn);  
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);  

const slugify = compose(  
    join('-'),  
    map(toLowerCase),  
    split(' '),  
    trim  
);  

console.log(slugify('  Hello World  ')); // 'hello-world'  
console.log(slugify('JavaScript Is Awesome')); // 'javascript-is-awesome'

6.2 管道 (Pipe)

管道与组合类似,但从左到右执行,更符合阅读习惯。

// pipe(f, g, h)(x) 等价于 h(g(f(x)))  
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);  

const addOne = x => x + 1;  
const double = x => x * 2;  
const square = x => x ** 2;  

const piped = pipe(double, addOne, square);  
// 执行顺序: double -> addOne -> square  
// 5 -> 10 -> 11 -> 121  
console.log(piped(5)); // 121  

// 实际应用示例:数据处理管道  
const users = [  
    { name: 'alice', age: 25, role: 'admin' },  
    { name: 'bob', age: 30, role: 'user' },  
    { name: 'charlie', age: 35, role: 'admin' },  
    { name: 'david', age: 28, role: 'user' }  
];  

// 辅助函数  
const filter = predicate => arr => arr.filter(predicate);  
const map = fn => arr => arr.map(fn);  
const sortBy = key => arr => [...arr].sort((a, b) => a[key] - b[key]);  
const take = n => arr => arr.slice(0, n);  

// 构建处理管道  
const getTopAdminNames = pipe(  
    filter(u => u.role === 'admin'),  // 筛选管理员  
    sortBy('age'),                      // 按年龄排序  
    map(u => u.name.toUpperCase()),    // 获取大写名字  
    take(2)                             // 取前两个  
);  

console.log(getTopAdminNames(users)); // ['ALICE', 'CHARLIE']

6.3 异步管道

// 支持异步函数的管道  
const pipeAsync = (...fns) => initialValue =>  
    fns.reduce(  
        (promise, fn) => promise.then(fn),  
        Promise.resolve(initialValue)  
    );  

// 模拟异步操作  
const fetchUser = async (id) => {  
    console.log(`获取用户 ${id}...`);  
    return { id, name: 'Alice', email: 'alice@example.com' };  
};  

const fetchUserPosts = async (user) => {  
    console.log(`获取 ${user.name} 的帖子...`);  
    return {  
        ...user,  
        posts: ['Post 1', 'Post 2', 'Post 3']  
    };  
};  

const formatUserData = async (data) => {  
    console.log('格式化数据...');  
    return {  
        displayName: data.name.toUpperCase(),  
        email: data.email,  
        postCount: data.posts.length  
    };  
};  

const processUser = pipeAsync(  
    fetchUser,  
    fetchUserPosts,  
    formatUserData  
);  

processUser(1).then(console.log);  
// 获取用户 1...  
// 获取 Alice 的帖子...  
// 格式化数据...  
// { displayName: 'ALICE', email: 'alice@example.com', postCount: 3 }

6.4 条件组合

// 创建条件执行函数  
const when = (predicate, fn) => x => predicate(x) ? fn(x) : x;  

const unless = (predicate, fn) => x => predicate(x) ? x : fn(x);  

const isEven = x => x % 2 === 0;  
const double = x => x * 2;  
const addOne = x => x + 1;  

const doubleIfEven = when(isEven, double);  
console.log(doubleIfEven(4)); // 8  
console.log(doubleIfEven(5)); // 5  

const addOneIfOdd = unless(isEven, addOne);  
console.log(addOneIfOdd(4)); // 4  
console.log(addOneIfOdd(5)); // 6  

// 分支组合  
const ifElse = (predicate, onTrue, onFalse) => x =>  
    predicate(x) ? onTrue(x) : onFalse(x);  

const processNumber = ifElse(  
    isEven,  
    x => `${x} 是偶数,乘2得 ${x * 2}`,  
    x => `${x} 是奇数,加1得 ${x + 1}`  
);  

console.log(processNumber(4)); // "4 是偶数,乘2得 8"  
console.log(processNumber(5)); // "5 是奇数,加1得 6"  

// 多条件分支  
const cond = (...pairs) => x => {  
    for (const [predicate, fn] of pairs) {  
        if (predicate(x)) {  
            return fn(x);  
        }  
    }  
    return x;  
};  

const classifyAge = cond(  
    [age => age < 13, () => '儿童'],  
    [age => age < 20, () => '青少年'],  
    [age => age < 60, () => '成年人'],  
    [() => true, () => '老年人']  
);  

console.log(classifyAge(8));  // "儿童"  
console.log(classifyAge(15)); // "青少年"  
console.log(classifyAge(30)); // "成年人"  
console.log(classifyAge(70)); // "老年人"

7. 柯里化与偏应用

7.1 柯里化 (Currying)

柯里化是将一个接受多个参数的函数转换为一系列接受单个参数的函数。

// 普通函数  
function add(a, b, c) {  
    return a + b + c;  
}  
console.log(add(1, 2, 3)); // 6  

// 手动柯里化  
function addCurried(a) {  
    return function(b) {  
        return function(c) {  
            return a + b + c;  
        };  
    };  
}  
console.log(addCurried(1)(2)(3)); // 6  

// 箭头函数版本  
const addCurriedArrow = a => b => c => a + b + c;  
console.log(addCurriedArrow(1)(2)(3)); // 6  

// 部分应用  
const add1 = addCurriedArrow(1);  
const add1and2 = add1(2);  
console.log(add1and2(3)); // 6  
console.log(add1and2(10)); // 13  

// 通用柯里化函数  
function curry(fn) {  
    return function curried(...args) {  
        if (args.length >= fn.length) {  
            return fn.apply(this, args);  
        }  
        return function(...moreArgs) {  
            return curried.apply(this, [...args, ...moreArgs]);  
        };  
    };  
}  

// 使用示例  
function multiply(a, b, c) {  
    return a * b * c;  
}  

const curriedMultiply = curry(multiply);  

console.log(curriedMultiply(2)(3)(4));     // 24  
console.log(curriedMultiply(2, 3)(4));     // 24  
console.log(curriedMultiply(2)(3, 4));     // 24  
console.log(curriedMultiply(2, 3, 4));     // 24  

// 创建专用函数  
const double = curriedMultiply(2)(1);  
console.log(double(5));  // 10  
console.log(double(10)); // 20

7.2 柯里化的实际应用

// 1. 配置化的API请求  
const request = curry((method, baseUrl, endpoint, data) => {  
    console.log(`${method} ${baseUrl}${endpoint}`, data);  
    // 实际实现会发送真实请求  
    return { method, url: `${baseUrl}${endpoint}`, data };  
});  

const apiRequest = request('POST')('https://api.example.com');  
const createUser = apiRequest('/users');  
const createPost = apiRequest('/posts');  

createUser({ name: 'Alice' }); // POST https://api.example.com/users { name: 'Alice' }  
createPost({ title: 'Hello' }); // POST https://api.example.com/posts { title: 'Hello' }  

// 2. 格式化函数  
const formatCurrency = curry((symbol, decimals, amount) => {  
    return `${symbol}${amount.toFixed(decimals)}`;  
});  

const formatUSD = formatCurrency('$')(2);  
const formatEUR = formatCurrency('€')(2);  
const formatJPY = formatCurrency('¥')(0);  

console.log(formatUSD(1234.5));   // "$1234.50"  
console.log(formatEUR(1234.5));   // "€1234.50"  
console.log(formatJPY(1234.5));   // "¥1235"  

// 3. 事件处理  
const handleEvent = curry((handler, eventName, element) => {  
    element.addEventListener(eventName, handler);  
    return () => element.removeEventListener(eventName, handler);  
});  

const logEvent = event => console.log('Event:', event.type);  
const addClickHandler = handleEvent(logEvent)('click');  

// addClickHandler(document.getElementById('myButton'));  

// 4. 验证函数  
const validate = curry((validator, errorMsg, value) => {  
    return validator(value)   
        ? { valid: true, value }  
        : { valid: false, error: errorMsg };  
});  

const isNotEmpty = value => value && value.trim().length > 0;  
const isEmail = value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);  
const minLength = min => value => value && value.length >= min;  

const validateRequired = validate(isNotEmpty)('此字段必填');  
const validateEmail = validate(isEmail)('请输入有效邮箱');  
const validatePassword = validate(minLength(8))('密码至少8位');  

console.log(validateRequired(''));        // { valid: false, error: '此字段必填' }  
console.log(validateRequired('hello'));   // { valid: true, value: 'hello' }  
console.log(validateEmail('test@a.com')); // { valid: true, value: 'test@a.com' }  
console.log(validatePassword('123'));     // { valid: false, error: '密码至少8位' }

7.3 偏应用 (Partial Application)

偏应用是固定函数的一部分参数,返回一个接受剩余参数的新函数。

// 简单的偏应用函数  
function partial(fn, ...presetArgs) {  
    return function(...laterArgs) {  
        return fn(...presetArgs, ...laterArgs);  
    };  
}  

function greet(greeting, punctuation, name) {  
    return `${greeting}, ${name}${punctuation}`;  
}  

const greetHello = partial(greet, 'Hello', '!');  
console.log(greetHello('Alice'));   // "Hello, Alice!"  
console.log(greetHello('Bob'));     // "Hello, Bob!"  

const greetHi = partial(greet, 'Hi');  
console.log(greetHi('?', 'Charlie')); // "Hi, Charlie?"  

// 使用占位符的偏应用  
const _ = Symbol('placeholder');  

function partialWithPlaceholder(fn, ...presetArgs) {  
    return function(...laterArgs) {  
        let laterIndex = 0;  
        const args = presetArgs.map(arg =>   
            arg === _ ? laterArgs[laterIndex++] : arg  
        );  
        return fn(...args, ...laterArgs.slice(laterIndex));  
    };  
}  

function subtract(a, b) {  
    return a - b;  
}  

const subtractFrom10 = partialWithPlaceholder(subtract, 10, _);  
console.log(subtractFrom10(3)); // 7  

const subtract5 = partialWithPlaceholder(subtract, _, 5);  
console.log(subtract5(10)); // 5  

// 使用 bind 实现偏应用  
function multiply(a, b, c) {  
    return a * b * c;  
}  

const multiplyBy2 = multiply.bind(null, 2);  
console.log(multiplyBy2(3, 4)); // 24  

const multiplyBy2And3 = multiply.bind(null, 2, 3);  
console.log(multiplyBy2And3(4)); // 24

7.4 柯里化 vs 偏应用

// 柯里化:将 f(a, b, c) 转换为 f(a)(b)(c)  
// 每次只接受一个参数  

// 偏应用:固定部分参数,返回接受剩余参数的函数  
// 可以一次固定多个参数  

function example(a, b, c, d) {  
    return a + b + c + d;  
}  

// 柯里化后  
const curriedExample = curry(example);  
console.log(curriedExample(1)(2)(3)(4));  // 10  
console.log(curriedExample(1, 2)(3)(4));  // 10 (这是增强版柯里化)  

// 偏应用后  
const partialExample = partial(example, 1, 2);  
console.log(partialExample(3, 4)); // 10  

// 柯里化的一步步调用  
const step1 = curriedExample(1);  
const step2 = step1(2);  
const step3 = step2(3);  
const result = step3(4);  
console.log(result); // 10

8. 实战案例

8.1 数据处理管道

// 电商订单处理系统  
const orders = [  
    { id: 1, customer: 'Alice', items: [  
        { name: 'iPhone', price: 999, quantity: 1 },  
        { name: 'Case', price: 29, quantity: 2 }  
    ], date: '2024-01-15', status: 'completed' },  
    { id: 2, customer: 'Bob', items: [  
        { name: 'MacBook', price: 1299, quantity: 1 }  
    ], date: '2024-01-16', status: 'pending' },  
    { id: 3, customer: 'Charlie', items: [  
        { name: 'AirPods', price: 199, quantity: 2 },  
        { name: 'Charger', price: 29, quantity: 1 }  
    ], date: '2024-01-15', status: 'completed' },  
    { id: 4, customer: 'Alice', items: [  
        { name: 'iPad', price: 799, quantity: 1 }  
    ], date: '2024-01-17', status: 'completed' }  
];  

// 工具函数  
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);  
const curry = fn => function curried(...args) {  
    return args.length >= fn.length   
        ? fn(...args)   
        : (...more) => curried(...args, ...more);  
};  

// 基础操作函数(柯里化)  
const filter = curry((predicate, arr) => arr.filter(predicate));  
const map = curry((fn, arr) => arr.map(fn));  
const reduce = curry((reducer, initial, arr) => arr.reduce(reducer, initial));  
const sortBy = curry((fn, arr) => [...arr].sort((a, b) => fn(a) - fn(b)));  
const groupBy = curry((keyFn, arr) =>   
    arr.reduce((acc, item) => {  
        const key = keyFn(item);  
        acc[key] = acc[key] || [];  
        acc[key].push(item);  
        return acc;  
    }, {})  
);  

// 业务逻辑函数  
const calculateOrderTotal = order => ({  
    ...order,  
    total: order.items.reduce((sum, item) => sum + item.price * item.quantity, 0)  
});  

const isCompleted = order => order.status === 'completed';  
const isCustomer = curry((name, order) => order.customer === name);  

// 1. 计算所有完成订单的总收入  
const totalRevenue = pipe(  
    filter(isCompleted),  
    map(calculateOrderTotal),  
    reduce((sum, order) => sum + order.total, 0)  
)(orders);  

console.log('总收入:', totalRevenue); // 2853  

// 2. 获取某个客户的订单统计  
const getCustomerStats = customerName => pipe(  
    filter(isCustomer(customerName)),  
    map(calculateOrderTotal),  
    orders => ({  
        customer: customerName,  
        orderCount: orders.length,  
        totalSpent: orders.reduce((sum, o) => sum + o.total, 0),  
        averageOrder: orders.length > 0   
            ? orders.reduce((sum, o) => sum + o.total, 0) / orders.length   
            : 0  
    })  
)(orders);  

console.log('Alice的统计:', getCustomerStats('Alice'));  
// { customer: 'Alice', orderCount: 2, totalSpent: 1856, averageOrder: 928 }  

// 3. 按日期分组的订单报告  
const ordersByDate = pipe(  
    map(calculateOrderTotal),  
    groupBy(order => order.date),  
    Object.entries,  
    map(([date, orders]) => ({  
        date,  
        orderCount: orders.length,  
        totalRevenue: orders.reduce((sum, o) => sum + o.total, 0)  
    })),  
    sortBy(report => new Date(report.date))  
)(orders);  

console.log('按日期分组:');  
ordersByDate.forEach(report => {  
    console.log(`  ${report.date}: ${report.orderCount}单, $${report.totalRevenue}`);  
});  

// 4. 热销商品排行  
const topProducts = pipe(  
    flatMap => orders.flatMap(o => o.items),  
    reduce((acc, item) => {  
        acc[item.name] = (acc[item.name] || 0) + item.quantity;  
        return acc;  
    }, {}),  
    Object.entries,  
    map(([name, quantity]) => ({ name, quantity })),  
    arr => arr.sort((a, b) => b.quantity - a.quantity)  
)(orders);  

console.log('热销商品:', topProducts);

8.2 表单验证系统

// 函数式表单验证  
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);  
const curry = fn => (...args) =>   
    args.length >= fn.length ? fn(...args) : curry(fn.bind(null, ...args));  

// 验证结果类型  
const Success = value => ({  
    isSuccess: true,  
    value,  
    map: fn => Success(fn(value)),  
    flatMap: fn => fn(value),  
    getOrElse: () => value,  
    fold: (onError, onSuccess) => onSuccess(value)  
});  

const Failure = errors => ({  
    isSuccess: false,  
    errors,  
    map: () => Failure(errors),  
    flatMap: () => Failure(errors),  
    getOrElse: defaultValue => defaultValue,  
    fold: (onError, onSuccess) => onError(errors)  
});  

// 基础验证器  
const createValidator = curry((predicate, errorMessage, value) =>   
    predicate(value) ? Success(value) : Failure([errorMessage])  
);  

// 组合验证器  
const combineValidators = (...validators) => value => {  
    const results = validators.map(v => v(value));  
    const errors = results  
        .filter(r => !r.isSuccess)  
        .flatMap(r => r.errors);  

    return errors.length === 0 ? Success(value) : Failure(errors);  
};  

// 具体验证器  
const isRequired = createValidator(  
    v => v !== null && v !== undefined && v !== '',  
    '此字段必填'  
);  

const isEmail = createValidator(  
    v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),  
    '请输入有效的邮箱地址'  
);  

const minLength = min => createValidator(  
    v => v && v.length >= min,  
    `长度至少为 ${min} 个字符`  
);  

const maxLength = max => createValidator(  
    v => !v || v.length <= max,  
    `长度不能超过 ${max} 个字符`  
);  

const isNumber = createValidator(  
    v => !isNaN(Number(v)),  
    '必须是数字'  
);  

const inRange = (min, max) => createValidator(  
    v => {  
        const num = Number(v);  
        return num >= min && num <= max;  
    },  
    `必须在 ${min} 到 ${max} 之间`  
);  

const matches = regex => createValidator(  
    v => regex.test(v),  
    '格式不正确'  
);  

// 验证整个表单  
const validateField = (fieldName, value, ...validators) => {  
    const result = combineValidators(...validators)(value);  
    return result.fold(  
        errors => ({ [fieldName]: { valid: false, errors } }),  
        value => ({ [fieldName]: { valid: true, value } })  
    );  
};  

const validateForm = schema => formData => {  
    const results = Object.entries(schema).map(([field, validators]) =>   
        validateField(field, formData[field], ...validators)  
    );  

    const merged = results.reduce((acc, r) => ({ ...acc, ...r }), {});  
    const isValid = Object.values(merged).every(r => r.valid);  

    return { isValid, fields: merged };  
};  

// 使用示例  
const userFormSchema = {  
    username: [isRequired, minLength(3), maxLength(20)],  
    email: [isRequired, isEmail],  
    age: [isRequired, isNumber, inRange(18, 120)],  
    password: [isRequired, minLength(8), matches(/[A-Z]/), matches(/[0-9]/)]  
};  

const validateUserForm = validateForm(userFormSchema);  

// 测试有效数据  
const validData = {  
    username: 'johndoe',  
    email: 'john@example.com',  
    age: '25',  
    password: 'SecurePass123'  
};  

console.log('有效数据验证:');  
console.log(validateUserForm(validData));  
// { isValid: true, fields: { username: { valid: true, value: 'johndoe' }, ... } }  

// 测试无效数据  
const invalidData = {  
    username: 'ab',  
    email: 'invalid-email',  
    age: '15',  
    password: 'weak'  
};  

console.log('\n无效数据验证:');  
const result = validateUserForm(invalidData);  
console.log('isValid:', result.isValid);  
Object.entries(result.fields).forEach(([field, data]) => {  
    if (!data.valid) {  
        console.log(`  ${field}: ${data.errors.join(', ')}`);  
    }  
});

8.3 状态管理

// 简易的函数式状态管理  
const createStore = (reducer, initialState) => {  
    let state = initialState;  
    const listeners = [];  

    return {  
        getState: () => state,  
        dispatch: action => {  
            state = reducer(state, action);  
            listeners.forEach(listener => listener(state));  
            return action;  
        },  
        subscribe: listener => {  
            listeners.push(listener);  
            return () => {  
                const index = listeners.indexOf(listener);  
                if (index > -1) {  
                    listeners.splice(index, 1);  
                }  
            };  
        }  
    };  
};  

// Action creators  
const createAction = type => payload => ({ type, payload });  

const actions = {  
    addTodo: createAction('ADD_TODO'),  
    toggleTodo: createAction('TOGGLE_TODO'),  
    removeTodo: createAction('REMOVE_TODO'),  
    setFilter: createAction('SET_FILTER')  
};  

// Reducer(纯函数)  
const initialState = {  
    todos: [],  
    filter: 'all', // 'all', 'active', 'completed'  
    nextId: 1  
};  

const todoReducer = (state = initialState, action) => {  
    switch (action.type) {  
        case 'ADD_TODO':  
            return {  
                ...state,  
                todos: [  
                    ...state.todos,  
                    { id: state.nextId, text: action.payload, completed: false }  
                ],  
                nextId: state.nextId + 1  
            };  

        case 'TOGGLE_TODO':  
            return {  
                ...state,  
                todos: state.todos.map(todo =>  
                    todo.id === action.payload  
                        ? { ...todo, completed: !todo.completed }  
                        : todo  
                )  
            };  

        case 'REMOVE_TODO':  
            return {  
                ...state,  
                todos: state.todos.filter(todo => todo.id !== action.payload)  
            };  

        case 'SET_FILTER':  
            return {  
                ...state,  
                filter: action.payload  
            };  

        default:  
            return state;  
    }  
};  

// Selectors(派生状态)  
const selectTodos = state => state.todos;  
const selectFilter = state => state.filter;  

const selectFilteredTodos = state => {  
    const todos = selectTodos(state);  
    const filter = selectFilter(state);  

    switch (filter) {  
        case 'active':  
            return todos.filter(t => !t.completed);  
        case 'completed':  
            return todos.filter(t => t.completed);  
        default:  
            return todos;  
    }  
};  

const selectStats = state => {  
    const todos = selectTodos(state);  
    return {  
        total: todos.length,  
        completed: todos.filter(t => t.completed).length,  
        active: todos.filter(t => !t.completed).length  
    };  
};  

// 使用  
const store = createStore(todoReducer, initialState);  

// 订阅状态变化  
const unsubscribe = store.subscribe(state => {  
    console.log('\n当前状态:');  
    console.log('Todos:', selectFilteredTodos(state));  
    console.log('统计:', selectStats(state));  
});  

// 派发 actions  
console.log('=== 添加待办事项 ===');  
store.dispatch(actions.addTodo('学习函数式编程'));  
store.dispatch(actions.addTodo('写代码'));  
store.dispatch(actions.addTodo('看文档'));  

console.log('\n=== 完成一项 ===');  
store.dispatch(actions.toggleTodo(1));  

console.log('\n=== 设置过滤器为 active ===');  
store.dispatch(actions.setFilter('active'));  

console.log('\n=== 删除一项 ===');  
store.dispatch(actions.removeTodo(2));  

// 取消订阅  
unsubscribe();

8.4 函数式工具库

// 创建一个小型函数式工具库  
const FP = {  
    // 核心函数  
    pipe: (...fns) => x => fns.reduce((acc, fn) => fn(acc), x),  
    compose: (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x),  

    curry: fn => {  
        const curried = (...args) =>  
            args.length >= fn.length  
                ? fn(...args)  
                : (...more) => curried(...args, ...more);  
        return curried;  
    },  

    // 数组操作  
    map: fn => arr => arr.map(fn),  
    filter: predicate => arr => arr.filter(predicate),  
    reduce: (fn, initial) => arr => arr.reduce(fn, initial),  
    find: predicate => arr => arr.find(predicate),  
    some: predicate => arr => arr.some(predicate),  
    every: predicate => arr => arr.every(predicate),  

    // 对象操作  
    prop: key => obj => obj[key],  
    assoc: (key, value) => obj => ({ ...obj, [key]: value }),  
    omit: keys => obj => {  
        const result = { ...obj };  
        keys.forEach(key => delete result[key]);  
        return result;  
    },  
    pick: keys => obj =>   
        keys.reduce((acc, key) => {  
            if (key in obj) acc[key] = obj[key];  
            return acc;  
        }, {}),  

    // 逻辑操作  
    not: fn => (...args) => !fn(...args),  
    and: (f, g) => (...args) => f(...args) && g(...args),  
    or: (f, g) => (...args) => f(...args) || g(...args),  

    // 条件操作  
    when: (predicate, fn) => x => predicate(x) ? fn(x) : x,  
    unless: (predicate, fn) => x => predicate(x) ? x : fn(x),  
    ifElse: (predicate, onTrue, onFalse) => x =>   
        predicate(x) ? onTrue(x) : onFalse(x),  

    // 比较操作  
    equals: a => b => a === b,  
    gt: a => b => b > a,  
    gte: a => b => b >= a,  
    lt: a => b => b < a,  
    lte: a => b => b <= a,  

    // 数学操作  
    add: a => b => a + b,  
    subtract: a => b => b - a,  
    multiply: a => b => a * b,  
    divide: a => b => b / a,  

    // 字符串操作  
    split: separator => str => str.split(separator),  
    join: separator => arr => arr.join(separator),  
    trim: str => str.trim(),  
    toLowerCase: str => str.toLowerCase(),  
    toUpperCase: str => str.toUpperCase(),  

    // 实用工具  
    identity: x => x,  
    constant: x => () => x,  
    tap: fn => x => { fn(x); return x; },  

    // 数组工具  
    head: arr => arr[0],  
    tail: arr => arr.slice(1),  
    last: arr => arr[arr.length - 1],  
    init: arr => arr.slice(0, -1),  
    take: n => arr => arr.slice(0, n),  
    drop: n => arr => arr.slice(n),  

    // 分组和排序  
    groupBy: keyFn => arr => arr.reduce((acc, item) => {  
        const key = keyFn(item);  
        acc[key] = acc[key] || [];  
        acc[key].push(item);  
        return acc;  
    }, {}),  

    sortBy: fn => arr => [...arr].sort((a, b) => {  
        const va = fn(a), vb = fn(b);  
        return va < vb ? -1 : va > vb ? 1 : 0;  
    }),  

    // 去重  
    uniq: arr => [...new Set(arr)],  
    uniqBy: fn => arr => {  
        const seen = new Set();  
        return arr.filter(item => {  
            const key = fn(item);  
            if (seen.has(key)) return false;  
            seen.add(key);  
            return true;  
        });  
    }  
};  

// 使用示例  
const { pipe, map, filter, reduce, prop, sortBy, take, groupBy } = FP;  

const users = [  
    { name: 'Alice', age: 25, department: 'Engineering' },  
    { name: 'Bob', age: 30, department: 'Marketing' },  
    { name: 'Charlie', age: 35, department: 'Engineering' },  
    { name: 'David', age: 28, department: 'Sales' },  
    { name: 'Eve', age: 32, department: 'Engineering' }  
];  

// 获取工程部门年龄最大的两个人的名字  
const result = pipe(  
    filter(user => user.department === 'Engineering'),  
    sortBy(prop('age')),  
    arr => arr.reverse(),  
    take(2),  
    map(prop('name'))  
)(users);  

console.log(result); // ['Charlie', 'Eve']  

// 按部门统计平均年龄  
const avgAgeByDept = pipe(  
    groupBy(prop('department')),  
    Object.entries,  
    map(([dept, members]) => ({  
        department: dept,  
        avgAge: members.reduce((sum, m) => sum + m.age, 0) / members.length,  
        count: members.length  
    })),  
    sortBy(prop('avgAge'))  
)(users);  

console.log(avgAgeByDept);

📖 总结

函数式编程的核心原则

  1. 使用纯函数 - 相同输入总是产生相同输出,无副作用
  2. 保持数据不可变 - 创建新数据而不是修改现有数据
  3. 使用高阶函数 - 函数可以作为参数传递和返回
  4. 函数组合 - 将简单函数组合成复杂函数
  5. 声明式编程 - 描述"做什么"而不是"怎么做"

学习路径建议

1. 基础阶段  
   ├── 理解纯函数概念  
   ├── 掌握 map、filter、reduce  
   └── 理解不可变性  

2. 进阶阶段  
   ├── 学习高阶函数模式  
   ├── 掌握函数组合和管道  
   └── 理解柯里化和偏应用  

3. 实践阶段  
   ├── 在项目中应用 FP 原则  
   ├── 使用 FP 库(Ramda、Lodash/fp)  
   └── 构建自己的工具函数库

推荐资源

记住:函数式编程不是全有或全无的选择。你可以在现有代码中逐步引入函数式概念,慢慢体会它带来的好处!

继续阅读 »

JavaScript 函数式编程完全指南

📚 目录

  1. 什么是函数式编程
  2. 纯函数
  3. 不可变性
  4. 高阶函数
  5. 核心数组方法
  6. 函数组合与管道
  7. 柯里化与偏应用
  8. 实战案例

1. 什么是函数式编程

函数式编程(Functional Programming,FP)是一种编程范式,它将计算视为数学函数的求值,强调:

  • 纯函数:相同输入总是产生相同输出
  • 不可变性:数据一旦创建就不能被修改
  • 函数组合:通过组合简单函数构建复杂功能
  • 避免副作用:函数不应该改变外部状态
// ❌ 命令式编程(告诉计算机"怎么做")  
const numbers = [1, 2, 3, 4, 5];  
const doubled = [];  
for (let i = 0; i < numbers.length; i++) {  
    doubled.push(numbers[i] * 2);  
}  
console.log(doubled); // [2, 4, 6, 8, 10]  

// ✅ 函数式编程(告诉计算机"做什么")  
const numbersFP = [1, 2, 3, 4, 5];  
const doubledFP = numbersFP.map(n => n * 2);  
console.log(doubledFP); // [2, 4, 6, 8, 10]

2. 纯函数

2.1 纯函数的定义

纯函数满足两个条件:

  1. 确定性:相同的输入永远返回相同的输出
  2. 无副作用:不修改外部状态,不依赖外部可变状态
// ✅ 纯函数示例  
function add(a, b) {  
    return a + b;  
}  

function multiply(a, b) {  
    return a * b;  
}  

function greet(name) {  
    return `Hello, ${name}!`;  
}  

// 多次调用,结果永远相同  
console.log(add(2, 3));      // 5  
console.log(add(2, 3));      // 5  
console.log(multiply(4, 5)); // 20  
console.log(greet('Alice')); // "Hello, Alice!"

2.2 非纯函数示例

// ❌ 非纯函数:依赖外部变量  
let taxRate = 0.1;  
function calculateTax(amount) {  
    return amount * taxRate; // 依赖外部变量 taxRate  
}  

console.log(calculateTax(100)); // 10  
taxRate = 0.2;  
console.log(calculateTax(100)); // 20 - 相同输入,不同输出!  

// ❌ 非纯函数:修改外部状态  
let total = 0;  
function addToTotal(value) {  
    total += value; // 副作用:修改了外部变量  
    return total;  
}  

console.log(addToTotal(5));  // 5  
console.log(addToTotal(5));  // 10 - 相同输入,不同输出!  

// ❌ 非纯函数:修改输入参数  
function addItem(cart, item) {  
    cart.push(item); // 副作用:修改了传入的数组  
    return cart;  
}  

const myCart = ['apple'];  
addItem(myCart, 'banana');  
console.log(myCart); // ['apple', 'banana'] - 原数组被修改了!

2.3 将非纯函数转换为纯函数

// ✅ 纯函数版本:将依赖作为参数传入  
function calculateTaxPure(amount, taxRate) {  
    return amount * taxRate;  
}  

console.log(calculateTaxPure(100, 0.1)); // 10  
console.log(calculateTaxPure(100, 0.2)); // 20  

// ✅ 纯函数版本:返回新值而不是修改外部状态  
function addToTotalPure(currentTotal, value) {  
    return currentTotal + value;  
}  

let totalPure = 0;  
totalPure = addToTotalPure(totalPure, 5);  
console.log(totalPure); // 5  

// ✅ 纯函数版本:返回新数组而不是修改原数组  
function addItemPure(cart, item) {  
    return [...cart, item]; // 返回新数组  
}  

const myCartPure = ['apple'];  
const newCart = addItemPure(myCartPure, 'banana');  
console.log(myCartPure); // ['apple'] - 原数组未被修改  
console.log(newCart);    // ['apple', 'banana']

2.4 纯函数的好处

// 1. 可测试性 - 纯函数非常容易测试  
function calculateDiscount(price, discountPercent) {  
    return price * (1 - discountPercent / 100);  
}  

// 测试用例  
console.log(calculateDiscount(100, 20) === 80);  // true  
console.log(calculateDiscount(50, 10) === 45);   // true  
console.log(calculateDiscount(200, 50) === 100); // true  

// 2. 可缓存性 - 相同输入总是相同输出,可以缓存结果  
function memoize(fn) {  
    const cache = new Map();  
    return function(...args) {  
        const key = JSON.stringify(args);  
        if (cache.has(key)) {  
            console.log('从缓存获取');  
            return cache.get(key);  
        }  
        console.log('计算中...');  
        const result = fn(...args);  
        cache.set(key, result);  
        return result;  
    };  
}  

function expensiveCalculation(n) {  
    // 模拟耗时计算  
    let result = 0;  
    for (let i = 0; i < n; i++) {  
        result += i;  
    }  
    return result;  
}  

const memoizedCalc = memoize(expensiveCalculation);  
console.log(memoizedCalc(10000)); // 计算中... 49995000  
console.log(memoizedCalc(10000)); // 从缓存获取 49995000  
console.log(memoizedCalc(5000));  // 计算中... 12497500

3. 不可变性

3.1 什么是不可变性

不可变性意味着数据一旦创建,就不能被修改。任何"修改"操作都会返回新的数据。

// ❌ 可变操作  
const person = { name: 'Alice', age: 25 };  
person.age = 26; // 直接修改原对象  
console.log(person); // { name: 'Alice', age: 26 }  

// ✅ 不可变操作  
const personImmutable = { name: 'Alice', age: 25 };  
const updatedPerson = { ...personImmutable, age: 26 }; // 创建新对象  
console.log(personImmutable); // { name: 'Alice', age: 25 } - 原对象不变  
console.log(updatedPerson);   // { name: 'Alice', age: 26 }

3.2 数组的不可变操作

const fruits = ['apple', 'banana', 'orange'];  

// ❌ 可变方法(会修改原数组)  
// push, pop, shift, unshift, splice, sort, reverse  

// ✅ 不可变方法(返回新数组)  
// map, filter, reduce, concat, slice, spread operator  

// 添加元素  
const withGrape = [...fruits, 'grape'];  
console.log(fruits);    // ['apple', 'banana', 'orange']  
console.log(withGrape); // ['apple', 'banana', 'orange', 'grape']  

// 在开头添加  
const withMango = ['mango', ...fruits];  
console.log(withMango); // ['mango', 'apple', 'banana', 'orange']  

// 删除元素(通过 filter)  
const withoutBanana = fruits.filter(f => f !== 'banana');  
console.log(withoutBanana); // ['apple', 'orange']  

// 修改元素(通过 map)  
const upperFruits = fruits.map(f => f.toUpperCase());  
console.log(upperFruits); // ['APPLE', 'BANANA', 'ORANGE']  

// 在指定位置插入  
const insertAt = (arr, index, item) => [  
    ...arr.slice(0, index),  
    item,  
    ...arr.slice(index)  
];  
console.log(insertAt(fruits, 1, 'kiwi')); // ['apple', 'kiwi', 'banana', 'orange']  

// 删除指定位置的元素  
const removeAt = (arr, index) => [  
    ...arr.slice(0, index),  
    ...arr.slice(index + 1)  
];  
console.log(removeAt(fruits, 1)); // ['apple', 'orange']  

// 更新指定位置的元素  
const updateAt = (arr, index, newValue) =>   
    arr.map((item, i) => i === index ? newValue : item);  
console.log(updateAt(fruits, 1, 'blueberry')); // ['apple', 'blueberry', 'orange']

3.3 对象的不可变操作

const user = {  
    name: 'Alice',  
    age: 25,  
    address: {  
        city: 'Beijing',  
        country: 'China'  
    },  
    hobbies: ['reading', 'coding']  
};  

// 更新顶层属性  
const userWithNewAge = { ...user, age: 26 };  
console.log(user.age);            // 25  
console.log(userWithNewAge.age);  // 26  

// 添加新属性  
const userWithEmail = { ...user, email: 'alice@example.com' };  
console.log(userWithEmail);  

// 删除属性  
const { age, ...userWithoutAge } = user;  
console.log(userWithoutAge); // { name: 'Alice', address: {...}, hobbies: [...] }  

// 更新嵌套属性(需要深层展开)  
const userWithNewCity = {  
    ...user,  
    address: {  
        ...user.address,  
        city: 'Shanghai'  
    }  
};  
console.log(user.address.city);           // 'Beijing'  
console.log(userWithNewCity.address.city); // 'Shanghai'  

// 更新数组属性  
const userWithNewHobby = {  
    ...user,  
    hobbies: [...user.hobbies, 'gaming']  
};  
console.log(user.hobbies);           // ['reading', 'coding']  
console.log(userWithNewHobby.hobbies); // ['reading', 'coding', 'gaming']

3.4 深度不可变更新工具函数

// 通用的深度更新函数  
function updatePath(obj, path, value) {  
    const keys = path.split('.');  

    if (keys.length === 1) {  
        return { ...obj, [keys[0]]: value };  
    }  

    const [first, ...rest] = keys;  
    return {  
        ...obj,  
        [first]: updatePath(obj[first], rest.join('.'), value)  
    };  
}  

const state = {  
    user: {  
        profile: {  
            name: 'Alice',  
            settings: {  
                theme: 'dark',  
                language: 'en'  
            }  
        }  
    }  
};  

const newState = updatePath(state, 'user.profile.settings.theme', 'light');  
console.log(state.user.profile.settings.theme);    // 'dark'  
console.log(newState.user.profile.settings.theme); // 'light'  

// 使用 Object.freeze 强制不可变(浅层)  
const frozenObj = Object.freeze({ a: 1, b: 2 });  
// frozenObj.a = 100; // 严格模式下会报错,非严格模式静默失败  
console.log(frozenObj.a); // 1  

// 深度冻结  
function deepFreeze(obj) {  
    Object.keys(obj).forEach(key => {  
        if (typeof obj[key] === 'object' && obj[key] !== null) {  
            deepFreeze(obj[key]);  
        }  
    });  
    return Object.freeze(obj);  
}  

const deepFrozenObj = deepFreeze({  
    a: 1,  
    b: { c: 2, d: { e: 3 } }  
});

4. 高阶函数

4.1 什么是高阶函数

高阶函数是至少满足以下条件之一的函数:

  1. 接受函数作为参数
  2. 返回一个函数
// 接受函数作为参数  
function executeOperation(a, b, operation) {  
    return operation(a, b);  
}  

const add = (x, y) => x + y;  
const multiply = (x, y) => x * y;  
const subtract = (x, y) => x - y;  

console.log(executeOperation(5, 3, add));      // 8  
console.log(executeOperation(5, 3, multiply)); // 15  
console.log(executeOperation(5, 3, subtract)); // 2  

// 返回一个函数  
function createMultiplier(factor) {  
    return function(number) {  
        return number * factor;  
    };  
}  

const double = createMultiplier(2);  
const triple = createMultiplier(3);  
const tenTimes = createMultiplier(10);  

console.log(double(5));   // 10  
console.log(triple(5));   // 15  
console.log(tenTimes(5)); // 50

4.2 常用高阶函数模式

// 1. 函数包装器 - 添加额外功能  
function withLogging(fn) {  
    return function(...args) {  
        console.log(`调用函数,参数: ${JSON.stringify(args)}`);  
        const result = fn(...args);  
        console.log(`返回结果: ${result}`);  
        return result;  
    };  
}  

function addNumbers(a, b) {  
    return a + b;  
}  

const addWithLogging = withLogging(addNumbers);  
addWithLogging(3, 4);  
// 输出:  
// 调用函数,参数: [3,4]  
// 返回结果: 7  

// 2. 函数计时器  
function withTiming(fn) {  
    return function(...args) {  
        const start = performance.now();  
        const result = fn(...args);  
        const end = performance.now();  
        console.log(`执行时间: ${(end - start).toFixed(2)}ms`);  
        return result;  
    };  
}  

function slowFunction() {  
    let sum = 0;  
    for (let i = 0; i < 1000000; i++) {  
        sum += i;  
    }  
    return sum;  
}  

const timedSlowFunction = withTiming(slowFunction);  
timedSlowFunction(); // 执行时间: x.xxms  

// 3. 只执行一次的函数  
function once(fn) {  
    let called = false;  
    let result;  

    return function(...args) {  
        if (!called) {  
            called = true;  
            result = fn(...args);  
        }  
        return result;  
    };  
}  

const initialize = once(() => {  
    console.log('初始化中...');  
    return { initialized: true };  
});  

console.log(initialize()); // 初始化中... { initialized: true }  
console.log(initialize()); // { initialized: true } - 不再打印"初始化中..."  
console.log(initialize()); // { initialized: true }  

// 4. 防抖函数  
function debounce(fn, delay) {  
    let timeoutId;  

    return function(...args) {  
        clearTimeout(timeoutId);  
        timeoutId = setTimeout(() => {  
            fn.apply(this, args);  
        }, delay);  
    };  
}  

const debouncedSearch = debounce((query) => {  
    console.log(`搜索: ${query}`);  
}, 300);  

// 快速连续调用,只有最后一次会执行  
debouncedSearch('a');  
debouncedSearch('ab');  
debouncedSearch('abc');  
// 300ms 后只输出: 搜索: abc  

// 5. 节流函数  
function throttle(fn, limit) {  
    let inThrottle = false;  

    return function(...args) {  
        if (!inThrottle) {  
            fn.apply(this, args);  
            inThrottle = true;  
            setTimeout(() => {  
                inThrottle = false;  
            }, limit);  
        }  
    };  
}  

const throttledScroll = throttle(() => {  
    console.log('处理滚动事件');  
}, 100);

4.3 创建专用函数

// 使用高阶函数创建专用函数  
function createValidator(validationFn, errorMessage) {  
    return function(value) {  
        if (validationFn(value)) {  
            return { valid: true, value };  
        }  
        return { valid: false, error: errorMessage };  
    };  
}  

const isNotEmpty = createValidator(  
    value => value && value.trim().length > 0,  
    '值不能为空'  
);  

const isEmail = createValidator(  
    value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),  
    '请输入有效的邮箱地址'  
);  

const isMinLength = (min) => createValidator(  
    value => value && value.length >= min,  
    `长度至少为 ${min} 个字符`  
);  

console.log(isNotEmpty('hello'));     // { valid: true, value: 'hello' }  
console.log(isNotEmpty(''));          // { valid: false, error: '值不能为空' }  
console.log(isEmail('test@mail.com')); // { valid: true, value: 'test@mail.com' }  
console.log(isEmail('invalid'));       // { valid: false, error: '请输入有效的邮箱地址' }  

const isMinLength5 = isMinLength(5);  
console.log(isMinLength5('hello'));    // { valid: true, value: 'hello' }  
console.log(isMinLength5('hi'));       // { valid: false, error: '长度至少为 5 个字符' }

5. 核心数组方法

5.1 map - 转换每个元素

// map 的基本用法  
const numbers = [1, 2, 3, 4, 5];  

// 简单转换  
const doubled = numbers.map(n => n * 2);  
console.log(doubled); // [2, 4, 6, 8, 10]  

const squared = numbers.map(n => n ** 2);  
console.log(squared); // [1, 4, 9, 16, 25]  

// 转换对象数组  
const users = [  
    { name: 'Alice', age: 25 },  
    { name: 'Bob', age: 30 },  
    { name: 'Charlie', age: 35 }  
];  

const names = users.map(user => user.name);  
console.log(names); // ['Alice', 'Bob', 'Charlie']  

const userCards = users.map(user => ({  
    ...user,  
    displayName: `${user.name} (${user.age}岁)`  
}));  
console.log(userCards);  
// [  
//   { name: 'Alice', age: 25, displayName: 'Alice (25岁)' },  
//   { name: 'Bob', age: 30, displayName: 'Bob (30岁)' },  
//   { name: 'Charlie', age: 35, displayName: 'Charlie (35岁)' }  
// ]  

// 使用索引参数  
const indexed = numbers.map((n, index) => `${index}: ${n}`);  
console.log(indexed); // ['0: 1', '1: 2', '2: 3', '3: 4', '4: 5']  

// 自己实现 map  
function myMap(arr, fn) {  
    const result = [];  
    for (let i = 0; i < arr.length; i++) {  
        result.push(fn(arr[i], i, arr));  
    }  
    return result;  
}  

console.log(myMap([1, 2, 3], x => x * 10)); // [10, 20, 30]

5.2 filter - 过滤元素

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];  

// 基本过滤  
const evens = numbers.filter(n => n % 2 === 0);  
console.log(evens); // [2, 4, 6, 8, 10]  

const odds = numbers.filter(n => n % 2 !== 0);  
console.log(odds); // [1, 3, 5, 7, 9]  

const greaterThan5 = numbers.filter(n => n > 5);  
console.log(greaterThan5); // [6, 7, 8, 9, 10]  

// 过滤对象数组  
const products = [  
    { name: 'iPhone', price: 999, inStock: true },  
    { name: 'iPad', price: 799, inStock: false },  
    { name: 'MacBook', price: 1299, inStock: true },  
    { name: 'AirPods', price: 199, inStock: true }  
];  

const inStockProducts = products.filter(p => p.inStock);  
console.log(inStockProducts);  
// [  
//   { name: 'iPhone', price: 999, inStock: true },  
//   { name: 'MacBook', price: 1299, inStock: true },  
//   { name: 'AirPods', price: 199, inStock: true }  
// ]  

const affordableProducts = products.filter(p => p.price < 1000);  
console.log(affordableProducts);  
// [  
//   { name: 'iPhone', price: 999, inStock: true },  
//   { name: 'iPad', price: 799, inStock: false },  
//   { name: 'AirPods', price: 199, inStock: true }  
// ]  

// 组合条件  
const affordableInStock = products.filter(p => p.price < 1000 && p.inStock);  
console.log(affordableInStock);  
// [  
//   { name: 'iPhone', price: 999, inStock: true },  
//   { name: 'AirPods', price: 199, inStock: true }  
// ]  

// 去除假值  
const mixedArray = [0, 1, '', 'hello', null, undefined, false, true, NaN];  
const truthyValues = mixedArray.filter(Boolean);  
console.log(truthyValues); // [1, 'hello', true]  

// 去重  
const duplicates = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4];  
const unique = duplicates.filter((item, index, arr) => arr.indexOf(item) === index);  
console.log(unique); // [1, 2, 3, 4]  

// 自己实现 filter  
function myFilter(arr, predicate) {  
    const result = [];  
    for (let i = 0; i < arr.length; i++) {  
        if (predicate(arr[i], i, arr)) {  
            result.push(arr[i]);  
        }  
    }  
    return result;  
}  

console.log(myFilter([1, 2, 3, 4, 5], x => x > 2)); // [3, 4, 5]

5.3 reduce - 归约为单一值

// 基本用法:求和  
const numbers = [1, 2, 3, 4, 5];  

const sum = numbers.reduce((accumulator, current) => {  
    console.log(`accumulator: ${accumulator}, current: ${current}`);  
    return accumulator + current;  
}, 0);  
// accumulator: 0, current: 1  
// accumulator: 1, current: 2  
// accumulator: 3, current: 3  
// accumulator: 6, current: 4  
// accumulator: 10, current: 5  
console.log(sum); // 15  

// 求乘积  
const product = numbers.reduce((acc, cur) => acc * cur, 1);  
console.log(product); // 120  

// 找最大值  
const max = numbers.reduce((acc, cur) => cur > acc ? cur : acc, -Infinity);  
console.log(max); // 5  

// 找最小值  
const min = numbers.reduce((acc, cur) => cur < acc ? cur : acc, Infinity);  
console.log(min); // 1  

// 数组转对象  
const users = [  
    { id: 1, name: 'Alice' },  
    { id: 2, name: 'Bob' },  
    { id: 3, name: 'Charlie' }  
];  

const userMap = users.reduce((acc, user) => {  
    acc[user.id] = user;  
    return acc;  
}, {});  
console.log(userMap);  
// {  
//   1: { id: 1, name: 'Alice' },  
//   2: { id: 2, name: 'Bob' },  
//   3: { id: 3, name: 'Charlie' }  
// }  

// 分组  
const people = [  
    { name: 'Alice', age: 25, city: 'Beijing' },  
    { name: 'Bob', age: 30, city: 'Shanghai' },  
    { name: 'Charlie', age: 25, city: 'Beijing' },  
    { name: 'David', age: 30, city: 'Beijing' }  
];  

const groupByAge = people.reduce((acc, person) => {  
    const key = person.age;  
    if (!acc[key]) {  
        acc[key] = [];  
    }  
    acc[key].push(person);  
    return acc;  
}, {});  
console.log(groupByAge);  
// {  
//   25: [{ name: 'Alice', ... }, { name: 'Charlie', ... }],  
//   30: [{ name: 'Bob', ... }, { name: 'David', ... }]  
// }  

// 通用分组函数  
function groupBy(arr, keyFn) {  
    return arr.reduce((acc, item) => {  
        const key = keyFn(item);  
        if (!acc[key]) {  
            acc[key] = [];  
        }  
        acc[key].push(item);  
        return acc;  
    }, {});  
}  

console.log(groupBy(people, p => p.city));  
// {  
//   Beijing: [Alice, Charlie, David],  
//   Shanghai: [Bob]  
// }  

// 统计出现次数  
const words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];  

const wordCount = words.reduce((acc, word) => {  
    acc[word] = (acc[word] || 0) + 1;  
    return acc;  
}, {});  
console.log(wordCount); // { apple: 3, banana: 2, orange: 1 }  

// 扁平化数组  
const nested = [[1, 2], [3, 4], [5, 6]];  
const flattened = nested.reduce((acc, arr) => [...acc, ...arr], []);  
console.log(flattened); // [1, 2, 3, 4, 5, 6]  

// 深度扁平化  
const deepNested = [[1, [2, 3]], [4, [5, [6, 7]]]];  

function flatten(arr) {  
    return arr.reduce((acc, item) => {  
        if (Array.isArray(item)) {  
            return [...acc, ...flatten(item)];  
        }  
        return [...acc, item];  
    }, []);  
}  

console.log(flatten(deepNested)); // [1, 2, 3, 4, 5, 6, 7]  

// 使用 reduce 实现 map  
function mapWithReduce(arr, fn) {  
    return arr.reduce((acc, item, index) => {  
        acc.push(fn(item, index, arr));  
        return acc;  
    }, []);  
}  

console.log(mapWithReduce([1, 2, 3], x => x * 2)); // [2, 4, 6]  

// 使用 reduce 实现 filter  
function filterWithReduce(arr, predicate) {  
    return arr.reduce((acc, item, index) => {  
        if (predicate(item, index, arr)) {  
            acc.push(item);  
        }  
        return acc;  
    }, []);  
}  

console.log(filterWithReduce([1, 2, 3, 4, 5], x => x > 2)); // [3, 4, 5]  

// 自己实现 reduce  
function myReduce(arr, reducer, initialValue) {  
    let accumulator = initialValue;  
    let startIndex = 0;  

    if (arguments.length < 3) {  
        accumulator = arr[0];  
        startIndex = 1;  
    }  

    for (let i = startIndex; i < arr.length; i++) {  
        accumulator = reducer(accumulator, arr[i], i, arr);  
    }  

    return accumulator;  
}  

console.log(myReduce([1, 2, 3, 4, 5], (a, b) => a + b, 0)); // 15

5.4 其他有用的数组方法

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];  

// find - 找到第一个满足条件的元素  
const firstEven = numbers.find(n => n % 2 === 0);  
console.log(firstEven); // 2  

const firstGreaterThan5 = numbers.find(n => n > 5);  
console.log(firstGreaterThan5); // 6  

// findIndex - 找到第一个满足条件的元素的索引  
const firstEvenIndex = numbers.findIndex(n => n % 2 === 0);  
console.log(firstEvenIndex); // 1  

// some - 检查是否至少有一个元素满足条件  
const hasEven = numbers.some(n => n % 2 === 0);  
console.log(hasEven); // true  

const hasNegative = numbers.some(n => n < 0);  
console.log(hasNegative); // false  

// every - 检查是否所有元素都满足条件  
const allPositive = numbers.every(n => n > 0);  
console.log(allPositive); // true  

const allEven = numbers.every(n => n % 2 === 0);  
console.log(allEven); // false  

// includes - 检查数组是否包含某个值  
console.log(numbers.includes(5));  // true  
console.log(numbers.includes(11)); // false  

// flat - 扁平化数组  
const nestedArray = [1, [2, 3], [4, [5, 6]]];  
console.log(nestedArray.flat());    // [1, 2, 3, 4, [5, 6]]  
console.log(nestedArray.flat(2));   // [1, 2, 3, 4, 5, 6]  
console.log(nestedArray.flat(Infinity)); // [1, 2, 3, 4, 5, 6]  

// flatMap - map + flat(1)  
const sentences = ['Hello World', 'Goodbye World'];  
const words = sentences.flatMap(s => s.split(' '));  
console.log(words); // ['Hello', 'World', 'Goodbye', 'World']  

// 实用示例:处理可能返回数组的映射  
const data = [1, 2, 3];  
const duplicated = data.flatMap(n => [n, n]);  
console.log(duplicated); // [1, 1, 2, 2, 3, 3]

5.5 方法链式调用

const orders = [  
    { id: 1, customer: 'Alice', items: ['apple', 'banana'], total: 25, status: 'completed' },  
    { id: 2, customer: 'Bob', items: ['orange'], total: 15, status: 'pending' },  
    { id: 3, customer: 'Charlie', items: ['apple', 'grape', 'melon'], total: 45, status: 'completed' },  
    { id: 4, customer: 'David', items: ['banana'], total: 10, status: 'cancelled' },  
    { id: 5, customer: 'Eve', items: ['apple', 'orange'], total: 30, status: 'completed' }  
];  

// 链式调用:筛选已完成订单,计算平均订单金额  
const averageCompletedOrderValue = orders  
    .filter(order => order.status === 'completed')  
    .map(order => order.total)  
    .reduce((sum, total, _, arr) => sum + total / arr.length, 0);  

console.log(averageCompletedOrderValue); // 33.33...  

// 获取所有完成订单的商品列表(去重)  
const completedOrderItems = orders  
    .filter(order => order.status === 'completed')  
    .flatMap(order => order.items)  
    .filter((item, index, arr) => arr.indexOf(item) === index);  

console.log(completedOrderItems); // ['apple', 'banana', 'grape', 'melon', 'orange']  

// 创建订单摘要  
const orderSummary = orders  
    .filter(order => order.status !== 'cancelled')  
    .map(order => ({  
        orderId: order.id,  
        customer: order.customer,  
        itemCount: order.items.length,  
        total: order.total  
    }))  
    .sort((a, b) => b.total - a.total);  

console.log(orderSummary);  
// [  
//   { orderId: 3, customer: 'Charlie', itemCount: 3, total: 45 },  
//   { orderId: 5, customer: 'Eve', itemCount: 2, total: 30 },  
//   { orderId: 1, customer: 'Alice', itemCount: 2, total: 25 },  
//   { orderId: 2, customer: 'Bob', itemCount: 1, total: 15 }  
// ]  

// 按状态分组统计  
const statusStats = orders.reduce((acc, order) => {  
    if (!acc[order.status]) {  
        acc[order.status] = { count: 0, totalValue: 0 };  
    }  
    acc[order.status].count++;  
    acc[order.status].totalValue += order.total;  
    return acc;  
}, {});  

console.log(statusStats);  
// {  
//   completed: { count: 3, totalValue: 100 },  
//   pending: { count: 1, totalValue: 15 },  
//   cancelled: { count: 1, totalValue: 10 }  
// }

6. 函数组合与管道

6.1 函数组合 (Compose)

函数组合是将多个函数合并成一个函数,从右到左执行。

// 基本概念  
// compose(f, g, h)(x) 等价于 f(g(h(x)))  

// 简单的两个函数组合  
const compose2 = (f, g) => x => f(g(x));  

const addOne = x => x + 1;  
const double = x => x * 2;  

const addOneThenDouble = compose2(double, addOne); // 先加1,再乘2  
console.log(addOneThenDouble(5)); // (5 + 1) * 2 = 12  

const doubleThenAddOne = compose2(addOne, double); // 先乘2,再加1  
console.log(doubleThenAddOne(5)); // (5 * 2) + 1 = 11  

// 通用的 compose 函数(支持多个函数)  
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);  

const square = x => x ** 2;  
const negate = x => -x;  

const composed = compose(negate, square, addOne, double);  
// 执行顺序: double -> addOne -> square -> negate  
// 5 -> 10 -> 11 -> 121 -> -121  
console.log(composed(5)); // -121  

// 更复杂的例子:处理字符串  
const trim = str => str.trim();  
const toLowerCase = str => str.toLowerCase();  
const split = delimiter => str => str.split(delimiter);  
const join = delimiter => arr => arr.join(delimiter);  
const map = fn => arr => arr.map(fn);  
const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);  

const slugify = compose(  
    join('-'),  
    map(toLowerCase),  
    split(' '),  
    trim  
);  

console.log(slugify('  Hello World  ')); // 'hello-world'  
console.log(slugify('JavaScript Is Awesome')); // 'javascript-is-awesome'

6.2 管道 (Pipe)

管道与组合类似,但从左到右执行,更符合阅读习惯。

// pipe(f, g, h)(x) 等价于 h(g(f(x)))  
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);  

const addOne = x => x + 1;  
const double = x => x * 2;  
const square = x => x ** 2;  

const piped = pipe(double, addOne, square);  
// 执行顺序: double -> addOne -> square  
// 5 -> 10 -> 11 -> 121  
console.log(piped(5)); // 121  

// 实际应用示例:数据处理管道  
const users = [  
    { name: 'alice', age: 25, role: 'admin' },  
    { name: 'bob', age: 30, role: 'user' },  
    { name: 'charlie', age: 35, role: 'admin' },  
    { name: 'david', age: 28, role: 'user' }  
];  

// 辅助函数  
const filter = predicate => arr => arr.filter(predicate);  
const map = fn => arr => arr.map(fn);  
const sortBy = key => arr => [...arr].sort((a, b) => a[key] - b[key]);  
const take = n => arr => arr.slice(0, n);  

// 构建处理管道  
const getTopAdminNames = pipe(  
    filter(u => u.role === 'admin'),  // 筛选管理员  
    sortBy('age'),                      // 按年龄排序  
    map(u => u.name.toUpperCase()),    // 获取大写名字  
    take(2)                             // 取前两个  
);  

console.log(getTopAdminNames(users)); // ['ALICE', 'CHARLIE']

6.3 异步管道

// 支持异步函数的管道  
const pipeAsync = (...fns) => initialValue =>  
    fns.reduce(  
        (promise, fn) => promise.then(fn),  
        Promise.resolve(initialValue)  
    );  

// 模拟异步操作  
const fetchUser = async (id) => {  
    console.log(`获取用户 ${id}...`);  
    return { id, name: 'Alice', email: 'alice@example.com' };  
};  

const fetchUserPosts = async (user) => {  
    console.log(`获取 ${user.name} 的帖子...`);  
    return {  
        ...user,  
        posts: ['Post 1', 'Post 2', 'Post 3']  
    };  
};  

const formatUserData = async (data) => {  
    console.log('格式化数据...');  
    return {  
        displayName: data.name.toUpperCase(),  
        email: data.email,  
        postCount: data.posts.length  
    };  
};  

const processUser = pipeAsync(  
    fetchUser,  
    fetchUserPosts,  
    formatUserData  
);  

processUser(1).then(console.log);  
// 获取用户 1...  
// 获取 Alice 的帖子...  
// 格式化数据...  
// { displayName: 'ALICE', email: 'alice@example.com', postCount: 3 }

6.4 条件组合

// 创建条件执行函数  
const when = (predicate, fn) => x => predicate(x) ? fn(x) : x;  

const unless = (predicate, fn) => x => predicate(x) ? x : fn(x);  

const isEven = x => x % 2 === 0;  
const double = x => x * 2;  
const addOne = x => x + 1;  

const doubleIfEven = when(isEven, double);  
console.log(doubleIfEven(4)); // 8  
console.log(doubleIfEven(5)); // 5  

const addOneIfOdd = unless(isEven, addOne);  
console.log(addOneIfOdd(4)); // 4  
console.log(addOneIfOdd(5)); // 6  

// 分支组合  
const ifElse = (predicate, onTrue, onFalse) => x =>  
    predicate(x) ? onTrue(x) : onFalse(x);  

const processNumber = ifElse(  
    isEven,  
    x => `${x} 是偶数,乘2得 ${x * 2}`,  
    x => `${x} 是奇数,加1得 ${x + 1}`  
);  

console.log(processNumber(4)); // "4 是偶数,乘2得 8"  
console.log(processNumber(5)); // "5 是奇数,加1得 6"  

// 多条件分支  
const cond = (...pairs) => x => {  
    for (const [predicate, fn] of pairs) {  
        if (predicate(x)) {  
            return fn(x);  
        }  
    }  
    return x;  
};  

const classifyAge = cond(  
    [age => age < 13, () => '儿童'],  
    [age => age < 20, () => '青少年'],  
    [age => age < 60, () => '成年人'],  
    [() => true, () => '老年人']  
);  

console.log(classifyAge(8));  // "儿童"  
console.log(classifyAge(15)); // "青少年"  
console.log(classifyAge(30)); // "成年人"  
console.log(classifyAge(70)); // "老年人"

7. 柯里化与偏应用

7.1 柯里化 (Currying)

柯里化是将一个接受多个参数的函数转换为一系列接受单个参数的函数。

// 普通函数  
function add(a, b, c) {  
    return a + b + c;  
}  
console.log(add(1, 2, 3)); // 6  

// 手动柯里化  
function addCurried(a) {  
    return function(b) {  
        return function(c) {  
            return a + b + c;  
        };  
    };  
}  
console.log(addCurried(1)(2)(3)); // 6  

// 箭头函数版本  
const addCurriedArrow = a => b => c => a + b + c;  
console.log(addCurriedArrow(1)(2)(3)); // 6  

// 部分应用  
const add1 = addCurriedArrow(1);  
const add1and2 = add1(2);  
console.log(add1and2(3)); // 6  
console.log(add1and2(10)); // 13  

// 通用柯里化函数  
function curry(fn) {  
    return function curried(...args) {  
        if (args.length >= fn.length) {  
            return fn.apply(this, args);  
        }  
        return function(...moreArgs) {  
            return curried.apply(this, [...args, ...moreArgs]);  
        };  
    };  
}  

// 使用示例  
function multiply(a, b, c) {  
    return a * b * c;  
}  

const curriedMultiply = curry(multiply);  

console.log(curriedMultiply(2)(3)(4));     // 24  
console.log(curriedMultiply(2, 3)(4));     // 24  
console.log(curriedMultiply(2)(3, 4));     // 24  
console.log(curriedMultiply(2, 3, 4));     // 24  

// 创建专用函数  
const double = curriedMultiply(2)(1);  
console.log(double(5));  // 10  
console.log(double(10)); // 20

7.2 柯里化的实际应用

// 1. 配置化的API请求  
const request = curry((method, baseUrl, endpoint, data) => {  
    console.log(`${method} ${baseUrl}${endpoint}`, data);  
    // 实际实现会发送真实请求  
    return { method, url: `${baseUrl}${endpoint}`, data };  
});  

const apiRequest = request('POST')('https://api.example.com');  
const createUser = apiRequest('/users');  
const createPost = apiRequest('/posts');  

createUser({ name: 'Alice' }); // POST https://api.example.com/users { name: 'Alice' }  
createPost({ title: 'Hello' }); // POST https://api.example.com/posts { title: 'Hello' }  

// 2. 格式化函数  
const formatCurrency = curry((symbol, decimals, amount) => {  
    return `${symbol}${amount.toFixed(decimals)}`;  
});  

const formatUSD = formatCurrency('$')(2);  
const formatEUR = formatCurrency('€')(2);  
const formatJPY = formatCurrency('¥')(0);  

console.log(formatUSD(1234.5));   // "$1234.50"  
console.log(formatEUR(1234.5));   // "€1234.50"  
console.log(formatJPY(1234.5));   // "¥1235"  

// 3. 事件处理  
const handleEvent = curry((handler, eventName, element) => {  
    element.addEventListener(eventName, handler);  
    return () => element.removeEventListener(eventName, handler);  
});  

const logEvent = event => console.log('Event:', event.type);  
const addClickHandler = handleEvent(logEvent)('click');  

// addClickHandler(document.getElementById('myButton'));  

// 4. 验证函数  
const validate = curry((validator, errorMsg, value) => {  
    return validator(value)   
        ? { valid: true, value }  
        : { valid: false, error: errorMsg };  
});  

const isNotEmpty = value => value && value.trim().length > 0;  
const isEmail = value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);  
const minLength = min => value => value && value.length >= min;  

const validateRequired = validate(isNotEmpty)('此字段必填');  
const validateEmail = validate(isEmail)('请输入有效邮箱');  
const validatePassword = validate(minLength(8))('密码至少8位');  

console.log(validateRequired(''));        // { valid: false, error: '此字段必填' }  
console.log(validateRequired('hello'));   // { valid: true, value: 'hello' }  
console.log(validateEmail('test@a.com')); // { valid: true, value: 'test@a.com' }  
console.log(validatePassword('123'));     // { valid: false, error: '密码至少8位' }

7.3 偏应用 (Partial Application)

偏应用是固定函数的一部分参数,返回一个接受剩余参数的新函数。

// 简单的偏应用函数  
function partial(fn, ...presetArgs) {  
    return function(...laterArgs) {  
        return fn(...presetArgs, ...laterArgs);  
    };  
}  

function greet(greeting, punctuation, name) {  
    return `${greeting}, ${name}${punctuation}`;  
}  

const greetHello = partial(greet, 'Hello', '!');  
console.log(greetHello('Alice'));   // "Hello, Alice!"  
console.log(greetHello('Bob'));     // "Hello, Bob!"  

const greetHi = partial(greet, 'Hi');  
console.log(greetHi('?', 'Charlie')); // "Hi, Charlie?"  

// 使用占位符的偏应用  
const _ = Symbol('placeholder');  

function partialWithPlaceholder(fn, ...presetArgs) {  
    return function(...laterArgs) {  
        let laterIndex = 0;  
        const args = presetArgs.map(arg =>   
            arg === _ ? laterArgs[laterIndex++] : arg  
        );  
        return fn(...args, ...laterArgs.slice(laterIndex));  
    };  
}  

function subtract(a, b) {  
    return a - b;  
}  

const subtractFrom10 = partialWithPlaceholder(subtract, 10, _);  
console.log(subtractFrom10(3)); // 7  

const subtract5 = partialWithPlaceholder(subtract, _, 5);  
console.log(subtract5(10)); // 5  

// 使用 bind 实现偏应用  
function multiply(a, b, c) {  
    return a * b * c;  
}  

const multiplyBy2 = multiply.bind(null, 2);  
console.log(multiplyBy2(3, 4)); // 24  

const multiplyBy2And3 = multiply.bind(null, 2, 3);  
console.log(multiplyBy2And3(4)); // 24

7.4 柯里化 vs 偏应用

// 柯里化:将 f(a, b, c) 转换为 f(a)(b)(c)  
// 每次只接受一个参数  

// 偏应用:固定部分参数,返回接受剩余参数的函数  
// 可以一次固定多个参数  

function example(a, b, c, d) {  
    return a + b + c + d;  
}  

// 柯里化后  
const curriedExample = curry(example);  
console.log(curriedExample(1)(2)(3)(4));  // 10  
console.log(curriedExample(1, 2)(3)(4));  // 10 (这是增强版柯里化)  

// 偏应用后  
const partialExample = partial(example, 1, 2);  
console.log(partialExample(3, 4)); // 10  

// 柯里化的一步步调用  
const step1 = curriedExample(1);  
const step2 = step1(2);  
const step3 = step2(3);  
const result = step3(4);  
console.log(result); // 10

8. 实战案例

8.1 数据处理管道

// 电商订单处理系统  
const orders = [  
    { id: 1, customer: 'Alice', items: [  
        { name: 'iPhone', price: 999, quantity: 1 },  
        { name: 'Case', price: 29, quantity: 2 }  
    ], date: '2024-01-15', status: 'completed' },  
    { id: 2, customer: 'Bob', items: [  
        { name: 'MacBook', price: 1299, quantity: 1 }  
    ], date: '2024-01-16', status: 'pending' },  
    { id: 3, customer: 'Charlie', items: [  
        { name: 'AirPods', price: 199, quantity: 2 },  
        { name: 'Charger', price: 29, quantity: 1 }  
    ], date: '2024-01-15', status: 'completed' },  
    { id: 4, customer: 'Alice', items: [  
        { name: 'iPad', price: 799, quantity: 1 }  
    ], date: '2024-01-17', status: 'completed' }  
];  

// 工具函数  
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);  
const curry = fn => function curried(...args) {  
    return args.length >= fn.length   
        ? fn(...args)   
        : (...more) => curried(...args, ...more);  
};  

// 基础操作函数(柯里化)  
const filter = curry((predicate, arr) => arr.filter(predicate));  
const map = curry((fn, arr) => arr.map(fn));  
const reduce = curry((reducer, initial, arr) => arr.reduce(reducer, initial));  
const sortBy = curry((fn, arr) => [...arr].sort((a, b) => fn(a) - fn(b)));  
const groupBy = curry((keyFn, arr) =>   
    arr.reduce((acc, item) => {  
        const key = keyFn(item);  
        acc[key] = acc[key] || [];  
        acc[key].push(item);  
        return acc;  
    }, {})  
);  

// 业务逻辑函数  
const calculateOrderTotal = order => ({  
    ...order,  
    total: order.items.reduce((sum, item) => sum + item.price * item.quantity, 0)  
});  

const isCompleted = order => order.status === 'completed';  
const isCustomer = curry((name, order) => order.customer === name);  

// 1. 计算所有完成订单的总收入  
const totalRevenue = pipe(  
    filter(isCompleted),  
    map(calculateOrderTotal),  
    reduce((sum, order) => sum + order.total, 0)  
)(orders);  

console.log('总收入:', totalRevenue); // 2853  

// 2. 获取某个客户的订单统计  
const getCustomerStats = customerName => pipe(  
    filter(isCustomer(customerName)),  
    map(calculateOrderTotal),  
    orders => ({  
        customer: customerName,  
        orderCount: orders.length,  
        totalSpent: orders.reduce((sum, o) => sum + o.total, 0),  
        averageOrder: orders.length > 0   
            ? orders.reduce((sum, o) => sum + o.total, 0) / orders.length   
            : 0  
    })  
)(orders);  

console.log('Alice的统计:', getCustomerStats('Alice'));  
// { customer: 'Alice', orderCount: 2, totalSpent: 1856, averageOrder: 928 }  

// 3. 按日期分组的订单报告  
const ordersByDate = pipe(  
    map(calculateOrderTotal),  
    groupBy(order => order.date),  
    Object.entries,  
    map(([date, orders]) => ({  
        date,  
        orderCount: orders.length,  
        totalRevenue: orders.reduce((sum, o) => sum + o.total, 0)  
    })),  
    sortBy(report => new Date(report.date))  
)(orders);  

console.log('按日期分组:');  
ordersByDate.forEach(report => {  
    console.log(`  ${report.date}: ${report.orderCount}单, $${report.totalRevenue}`);  
});  

// 4. 热销商品排行  
const topProducts = pipe(  
    flatMap => orders.flatMap(o => o.items),  
    reduce((acc, item) => {  
        acc[item.name] = (acc[item.name] || 0) + item.quantity;  
        return acc;  
    }, {}),  
    Object.entries,  
    map(([name, quantity]) => ({ name, quantity })),  
    arr => arr.sort((a, b) => b.quantity - a.quantity)  
)(orders);  

console.log('热销商品:', topProducts);

8.2 表单验证系统

// 函数式表单验证  
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);  
const curry = fn => (...args) =>   
    args.length >= fn.length ? fn(...args) : curry(fn.bind(null, ...args));  

// 验证结果类型  
const Success = value => ({  
    isSuccess: true,  
    value,  
    map: fn => Success(fn(value)),  
    flatMap: fn => fn(value),  
    getOrElse: () => value,  
    fold: (onError, onSuccess) => onSuccess(value)  
});  

const Failure = errors => ({  
    isSuccess: false,  
    errors,  
    map: () => Failure(errors),  
    flatMap: () => Failure(errors),  
    getOrElse: defaultValue => defaultValue,  
    fold: (onError, onSuccess) => onError(errors)  
});  

// 基础验证器  
const createValidator = curry((predicate, errorMessage, value) =>   
    predicate(value) ? Success(value) : Failure([errorMessage])  
);  

// 组合验证器  
const combineValidators = (...validators) => value => {  
    const results = validators.map(v => v(value));  
    const errors = results  
        .filter(r => !r.isSuccess)  
        .flatMap(r => r.errors);  

    return errors.length === 0 ? Success(value) : Failure(errors);  
};  

// 具体验证器  
const isRequired = createValidator(  
    v => v !== null && v !== undefined && v !== '',  
    '此字段必填'  
);  

const isEmail = createValidator(  
    v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),  
    '请输入有效的邮箱地址'  
);  

const minLength = min => createValidator(  
    v => v && v.length >= min,  
    `长度至少为 ${min} 个字符`  
);  

const maxLength = max => createValidator(  
    v => !v || v.length <= max,  
    `长度不能超过 ${max} 个字符`  
);  

const isNumber = createValidator(  
    v => !isNaN(Number(v)),  
    '必须是数字'  
);  

const inRange = (min, max) => createValidator(  
    v => {  
        const num = Number(v);  
        return num >= min && num <= max;  
    },  
    `必须在 ${min} 到 ${max} 之间`  
);  

const matches = regex => createValidator(  
    v => regex.test(v),  
    '格式不正确'  
);  

// 验证整个表单  
const validateField = (fieldName, value, ...validators) => {  
    const result = combineValidators(...validators)(value);  
    return result.fold(  
        errors => ({ [fieldName]: { valid: false, errors } }),  
        value => ({ [fieldName]: { valid: true, value } })  
    );  
};  

const validateForm = schema => formData => {  
    const results = Object.entries(schema).map(([field, validators]) =>   
        validateField(field, formData[field], ...validators)  
    );  

    const merged = results.reduce((acc, r) => ({ ...acc, ...r }), {});  
    const isValid = Object.values(merged).every(r => r.valid);  

    return { isValid, fields: merged };  
};  

// 使用示例  
const userFormSchema = {  
    username: [isRequired, minLength(3), maxLength(20)],  
    email: [isRequired, isEmail],  
    age: [isRequired, isNumber, inRange(18, 120)],  
    password: [isRequired, minLength(8), matches(/[A-Z]/), matches(/[0-9]/)]  
};  

const validateUserForm = validateForm(userFormSchema);  

// 测试有效数据  
const validData = {  
    username: 'johndoe',  
    email: 'john@example.com',  
    age: '25',  
    password: 'SecurePass123'  
};  

console.log('有效数据验证:');  
console.log(validateUserForm(validData));  
// { isValid: true, fields: { username: { valid: true, value: 'johndoe' }, ... } }  

// 测试无效数据  
const invalidData = {  
    username: 'ab',  
    email: 'invalid-email',  
    age: '15',  
    password: 'weak'  
};  

console.log('\n无效数据验证:');  
const result = validateUserForm(invalidData);  
console.log('isValid:', result.isValid);  
Object.entries(result.fields).forEach(([field, data]) => {  
    if (!data.valid) {  
        console.log(`  ${field}: ${data.errors.join(', ')}`);  
    }  
});

8.3 状态管理

// 简易的函数式状态管理  
const createStore = (reducer, initialState) => {  
    let state = initialState;  
    const listeners = [];  

    return {  
        getState: () => state,  
        dispatch: action => {  
            state = reducer(state, action);  
            listeners.forEach(listener => listener(state));  
            return action;  
        },  
        subscribe: listener => {  
            listeners.push(listener);  
            return () => {  
                const index = listeners.indexOf(listener);  
                if (index > -1) {  
                    listeners.splice(index, 1);  
                }  
            };  
        }  
    };  
};  

// Action creators  
const createAction = type => payload => ({ type, payload });  

const actions = {  
    addTodo: createAction('ADD_TODO'),  
    toggleTodo: createAction('TOGGLE_TODO'),  
    removeTodo: createAction('REMOVE_TODO'),  
    setFilter: createAction('SET_FILTER')  
};  

// Reducer(纯函数)  
const initialState = {  
    todos: [],  
    filter: 'all', // 'all', 'active', 'completed'  
    nextId: 1  
};  

const todoReducer = (state = initialState, action) => {  
    switch (action.type) {  
        case 'ADD_TODO':  
            return {  
                ...state,  
                todos: [  
                    ...state.todos,  
                    { id: state.nextId, text: action.payload, completed: false }  
                ],  
                nextId: state.nextId + 1  
            };  

        case 'TOGGLE_TODO':  
            return {  
                ...state,  
                todos: state.todos.map(todo =>  
                    todo.id === action.payload  
                        ? { ...todo, completed: !todo.completed }  
                        : todo  
                )  
            };  

        case 'REMOVE_TODO':  
            return {  
                ...state,  
                todos: state.todos.filter(todo => todo.id !== action.payload)  
            };  

        case 'SET_FILTER':  
            return {  
                ...state,  
                filter: action.payload  
            };  

        default:  
            return state;  
    }  
};  

// Selectors(派生状态)  
const selectTodos = state => state.todos;  
const selectFilter = state => state.filter;  

const selectFilteredTodos = state => {  
    const todos = selectTodos(state);  
    const filter = selectFilter(state);  

    switch (filter) {  
        case 'active':  
            return todos.filter(t => !t.completed);  
        case 'completed':  
            return todos.filter(t => t.completed);  
        default:  
            return todos;  
    }  
};  

const selectStats = state => {  
    const todos = selectTodos(state);  
    return {  
        total: todos.length,  
        completed: todos.filter(t => t.completed).length,  
        active: todos.filter(t => !t.completed).length  
    };  
};  

// 使用  
const store = createStore(todoReducer, initialState);  

// 订阅状态变化  
const unsubscribe = store.subscribe(state => {  
    console.log('\n当前状态:');  
    console.log('Todos:', selectFilteredTodos(state));  
    console.log('统计:', selectStats(state));  
});  

// 派发 actions  
console.log('=== 添加待办事项 ===');  
store.dispatch(actions.addTodo('学习函数式编程'));  
store.dispatch(actions.addTodo('写代码'));  
store.dispatch(actions.addTodo('看文档'));  

console.log('\n=== 完成一项 ===');  
store.dispatch(actions.toggleTodo(1));  

console.log('\n=== 设置过滤器为 active ===');  
store.dispatch(actions.setFilter('active'));  

console.log('\n=== 删除一项 ===');  
store.dispatch(actions.removeTodo(2));  

// 取消订阅  
unsubscribe();

8.4 函数式工具库

// 创建一个小型函数式工具库  
const FP = {  
    // 核心函数  
    pipe: (...fns) => x => fns.reduce((acc, fn) => fn(acc), x),  
    compose: (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x),  

    curry: fn => {  
        const curried = (...args) =>  
            args.length >= fn.length  
                ? fn(...args)  
                : (...more) => curried(...args, ...more);  
        return curried;  
    },  

    // 数组操作  
    map: fn => arr => arr.map(fn),  
    filter: predicate => arr => arr.filter(predicate),  
    reduce: (fn, initial) => arr => arr.reduce(fn, initial),  
    find: predicate => arr => arr.find(predicate),  
    some: predicate => arr => arr.some(predicate),  
    every: predicate => arr => arr.every(predicate),  

    // 对象操作  
    prop: key => obj => obj[key],  
    assoc: (key, value) => obj => ({ ...obj, [key]: value }),  
    omit: keys => obj => {  
        const result = { ...obj };  
        keys.forEach(key => delete result[key]);  
        return result;  
    },  
    pick: keys => obj =>   
        keys.reduce((acc, key) => {  
            if (key in obj) acc[key] = obj[key];  
            return acc;  
        }, {}),  

    // 逻辑操作  
    not: fn => (...args) => !fn(...args),  
    and: (f, g) => (...args) => f(...args) && g(...args),  
    or: (f, g) => (...args) => f(...args) || g(...args),  

    // 条件操作  
    when: (predicate, fn) => x => predicate(x) ? fn(x) : x,  
    unless: (predicate, fn) => x => predicate(x) ? x : fn(x),  
    ifElse: (predicate, onTrue, onFalse) => x =>   
        predicate(x) ? onTrue(x) : onFalse(x),  

    // 比较操作  
    equals: a => b => a === b,  
    gt: a => b => b > a,  
    gte: a => b => b >= a,  
    lt: a => b => b < a,  
    lte: a => b => b <= a,  

    // 数学操作  
    add: a => b => a + b,  
    subtract: a => b => b - a,  
    multiply: a => b => a * b,  
    divide: a => b => b / a,  

    // 字符串操作  
    split: separator => str => str.split(separator),  
    join: separator => arr => arr.join(separator),  
    trim: str => str.trim(),  
    toLowerCase: str => str.toLowerCase(),  
    toUpperCase: str => str.toUpperCase(),  

    // 实用工具  
    identity: x => x,  
    constant: x => () => x,  
    tap: fn => x => { fn(x); return x; },  

    // 数组工具  
    head: arr => arr[0],  
    tail: arr => arr.slice(1),  
    last: arr => arr[arr.length - 1],  
    init: arr => arr.slice(0, -1),  
    take: n => arr => arr.slice(0, n),  
    drop: n => arr => arr.slice(n),  

    // 分组和排序  
    groupBy: keyFn => arr => arr.reduce((acc, item) => {  
        const key = keyFn(item);  
        acc[key] = acc[key] || [];  
        acc[key].push(item);  
        return acc;  
    }, {}),  

    sortBy: fn => arr => [...arr].sort((a, b) => {  
        const va = fn(a), vb = fn(b);  
        return va < vb ? -1 : va > vb ? 1 : 0;  
    }),  

    // 去重  
    uniq: arr => [...new Set(arr)],  
    uniqBy: fn => arr => {  
        const seen = new Set();  
        return arr.filter(item => {  
            const key = fn(item);  
            if (seen.has(key)) return false;  
            seen.add(key);  
            return true;  
        });  
    }  
};  

// 使用示例  
const { pipe, map, filter, reduce, prop, sortBy, take, groupBy } = FP;  

const users = [  
    { name: 'Alice', age: 25, department: 'Engineering' },  
    { name: 'Bob', age: 30, department: 'Marketing' },  
    { name: 'Charlie', age: 35, department: 'Engineering' },  
    { name: 'David', age: 28, department: 'Sales' },  
    { name: 'Eve', age: 32, department: 'Engineering' }  
];  

// 获取工程部门年龄最大的两个人的名字  
const result = pipe(  
    filter(user => user.department === 'Engineering'),  
    sortBy(prop('age')),  
    arr => arr.reverse(),  
    take(2),  
    map(prop('name'))  
)(users);  

console.log(result); // ['Charlie', 'Eve']  

// 按部门统计平均年龄  
const avgAgeByDept = pipe(  
    groupBy(prop('department')),  
    Object.entries,  
    map(([dept, members]) => ({  
        department: dept,  
        avgAge: members.reduce((sum, m) => sum + m.age, 0) / members.length,  
        count: members.length  
    })),  
    sortBy(prop('avgAge'))  
)(users);  

console.log(avgAgeByDept);

📖 总结

函数式编程的核心原则

  1. 使用纯函数 - 相同输入总是产生相同输出,无副作用
  2. 保持数据不可变 - 创建新数据而不是修改现有数据
  3. 使用高阶函数 - 函数可以作为参数传递和返回
  4. 函数组合 - 将简单函数组合成复杂函数
  5. 声明式编程 - 描述"做什么"而不是"怎么做"

学习路径建议

1. 基础阶段  
   ├── 理解纯函数概念  
   ├── 掌握 map、filter、reduce  
   └── 理解不可变性  

2. 进阶阶段  
   ├── 学习高阶函数模式  
   ├── 掌握函数组合和管道  
   └── 理解柯里化和偏应用  

3. 实践阶段  
   ├── 在项目中应用 FP 原则  
   ├── 使用 FP 库(Ramda、Lodash/fp)  
   └── 构建自己的工具函数库

推荐资源

记住:函数式编程不是全有或全无的选择。你可以在现有代码中逐步引入函数式概念,慢慢体会它带来的好处!

收起阅读 »

鸿蒙征文大赛获奖名单公示

鸿蒙征文

本次鸿蒙征文大赛收到大量优质投稿,开发者们积极分享了基于 uni-app 的鸿蒙开发实践经验,完整征文列表详见:鸿蒙征文

获奖名单

一等奖

获奖作者 获奖文章
yuhespace 【鸿蒙征文】从创业小白到省赛获奖:我们用 uni-app 做出了"校园人人帮"
d***@qq.com 【鸿蒙征文】 炸裂!我用uni-app三天让旧应用通杀鸿蒙Next+元服务,华为商店已上架!2W奖励金即将到账。
l***@163.com 精膳通智慧食堂的鸿蒙开发之旅:因 uni-app 而简化,为国产生态而让利
clearliu 从痛点到产品:uni-app x + HarmonyOS打造房产投资管理系统全记录
nutpi 从零到一:使用 uni-app x 开发鸿蒙 GitCode 目录树生成器

二等奖

获奖作者 获奖文章
哦哦哦哈哈 从新建文件夹开始,用一个项目实战讲透uni-app鸿蒙开发到上架全流程(多图、细节)
小疯子呵 表情包搜索助手:uni-app 鸿蒙应用开发全流程解析
小白2023 低成本入局鸿蒙生态!Uniapp 适配鸿蒙实战分享,一次编码跑通多端
i***@alone88.cn 【鸿蒙征文】uni-app 鸿蒙开发实践:华为账号一键登录集成之路
GraceUI 【鸿蒙征文】关于鸿蒙折叠屏(宽屏设备)适配的一些分享
maq 鸿蒙企业应用内部分发打包教程
COOL团队 【鸿蒙征文】折腾鸿蒙分享功能的那些事儿
imseantang 【鸿蒙征文】从零到上架:用 uni-app 开发鸿蒙习惯养成应用习惯修仙的全流程实践
m***@163.com 一个人用 uni-app 做鸿蒙日语学习 App 的踩坑之旅
不如摸鱼去 uni-app 也能开发纯血鸿蒙 App?使用 wot-starter 这样快速上手!
陌上华年 【鸿蒙征文】UniApp(X) 让鸿蒙开发触手可及 —— LimeUI 组件库开发简录
tmui 【鸿蒙征文】记一次鸿蒙Next原生插件开发与Uniapp调用实战之弹层开发(tmui4x中的xToasts弹层)(含源码附件)
1***@qq.com 【鸿蒙征文】uniapp 实现鸿蒙自定义扫码界面
2***@qq.com 【鸿蒙征文】从鸿蒙适配到迎娶白富美:一个程序员的逆袭之路
VK168 【鸿蒙征文】uni-app鸿蒙上架必备技能:应用适配深色模式

三等奖

获奖作者 获奖文章
5***@qq.com 从uni-app到鸿蒙:爱影家影视App的跨平台开发实践
1***@qq.com 从Web到鸿蒙:uni-app x 开发踩坑与成长实录
诗酒趁闲 【鸿蒙征文】重拾儿时趣味的古诗 App 的开发过程记录
陌上华年 【鸿蒙征文】从零实现 uni-app 鸿蒙平台 TTS 插件:UTS 开发实践指南
知青 经验分享 鸿蒙里的权限设置,如何获取、查询权限
1***@qq.com 【鸿蒙征文】分享我 uniapp 集成鸿蒙企业微信的经验
暴走莫扎特 Uniapp 的鸿蒙 next 应用中隐藏和显示系统状态栏
正知名 【鸿蒙征文】从现在起,你的非原生弹窗"组件"们(自定义Toast、Modal等)只需要配置一次!
刘星 1024星光不负,码向未来——以 uni-app 筑梦鸿蒙生态我的uni-app鸿蒙开发之旅
青衫行者 【鸿蒙征文】星光不负,码向未来:uni-app + uniCloud 赋能社区管理系统的高效适配与生态融合实践
坚果派 摩尔斯电码转换器
小疯子呵 用uni-app搞了个足球战术板,踩了不少canvas坑,分享一下经验
VK168 【鸿蒙征文】uni-app 现有 UI 库兼容鸿蒙系统开发指南
3***@qq.com uni-app 打通鸿蒙从开发到上架:一条龙落地指南
程序媛夏天 使用 uni-app x 在 HarmonyOS 平台开发波斯历转换器的实践与思考
8***@qq.com 在 UniApp 中用 UTS 封装钉钉登录(HarmonyOS)
pickled 解决uniapp鸿蒙适配深色模式的问题
xxiaohe0601 【鸿蒙征文】Uni ECharts 2.1 发布:正式支持鸿蒙,零成本迁移、全平台兼容、跨端开发零负担!
Isaacedvr 鸿图初展,蒙学破茧:与uniapp-x和tmui4.0共闯鸿蒙Next实战录
Deminic 从零到一开发鸿蒙6原生时钟应用:uni-app x 完全实战指南
奇风2016 【鸿蒙征文】uniapp 赋能鸿蒙,应用开发速度翻倍,十天速通4个APP
i***@alone88.cn uni-app 鸿蒙应用开发实战:优雅解决文件下载存储路径问题
小疯子呵 【弧形导航栏】中间凸起按钮和消息未读角标,支持鸿蒙
8***@qq.com UniApp 项目鸿蒙上线
y***@163.com uniapp极速上手鸿蒙开发
8***@qq.com 鸿蒙 UTS 插件开发实战:屏幕方向控制插件的完整实现
用户2914143 使用 uni-app x 开发2048游戏适配鸿蒙6
威龙 【鸿蒙征文】使用 UTS 插件优雅实现"一键退出应用"功能
阿岳 【鸿蒙征文】使用 uni-app 开发鸿蒙 App,如何实现 H5 网页和 App 互通讯?
知青 鸿蒙 UTS 插件使用三方依赖、本地依赖

方向二特别奖

获奖作者 获奖文章
yuhespace 【鸿蒙征文】从创业小白到省赛获奖:我们用 uni-app 做出了"校园人人帮"
d***@qq.com 【鸿蒙征文】 炸裂!我用uni-app三天让旧应用通杀鸿蒙Next+元服务,华为商店已上架!2W奖励金即将到账。
imseantang 【鸿蒙征文】从零到上架:用 uni-app 开发鸿蒙习惯养成应用习惯修仙的全流程实践
iq2s 精膳通智慧食堂的鸿蒙开发之旅:因 uni-app 而简化,为国产生态而让利
clearliu 从痛点到产品:uni-app x + HarmonyOS打造房产投资管理系统全记录
5***@qq.com 从uni-app到鸿蒙:爱影家影视App的跨平台开发实践
m***@163.com 一个人用 uni-app 做鸿蒙日语学习 App 的踩坑之旅
8***@qq.com 集成鸿蒙五大核心能力,打造高性能生活服务类元服务
nutpi 从零到一:使用 uni-app x 开发鸿蒙 GitCode 目录树生成器
诗酒趁闲 【鸿蒙征文】重拾儿时趣味的古诗 App 的开发过程记录

奖项设置

一等奖(5名)
如上奖品为二选一,随机发放;

  • 华为手环7-NFC版
  • 华为智能水杯450ml + 露营灯-无级调光-太阳能+Type-C充电 组合

二等奖(15名)
露营灯-无级调光,太阳能 或 Type-C充电,随机发货

三等奖(30名)
HUAWEI 无线蓝牙鼠标-双模办公-灰色 (价值99元)

方向二特别奖(10名)
针对【方向二·案例实战】的优质投稿,额外设立10份 华为手环 9 NFC版作为激励!


奖品领取

请各位获奖作者尽快提交自己的邮寄地址,我们会陆续联系获奖人员发放奖品。

邮寄地址提交方式:登录 ask社区,点击右上角个人头像,进入设置界面,设置界面下方补充快递邮寄地址。


感谢所有参与本次征文活动的开发者,你们的分享让uni-app和鸿蒙生态更加繁荣!

继续阅读 »

本次鸿蒙征文大赛收到大量优质投稿,开发者们积极分享了基于 uni-app 的鸿蒙开发实践经验,完整征文列表详见:鸿蒙征文

获奖名单

一等奖

获奖作者 获奖文章
yuhespace 【鸿蒙征文】从创业小白到省赛获奖:我们用 uni-app 做出了"校园人人帮"
d***@qq.com 【鸿蒙征文】 炸裂!我用uni-app三天让旧应用通杀鸿蒙Next+元服务,华为商店已上架!2W奖励金即将到账。
l***@163.com 精膳通智慧食堂的鸿蒙开发之旅:因 uni-app 而简化,为国产生态而让利
clearliu 从痛点到产品:uni-app x + HarmonyOS打造房产投资管理系统全记录
nutpi 从零到一:使用 uni-app x 开发鸿蒙 GitCode 目录树生成器

二等奖

获奖作者 获奖文章
哦哦哦哈哈 从新建文件夹开始,用一个项目实战讲透uni-app鸿蒙开发到上架全流程(多图、细节)
小疯子呵 表情包搜索助手:uni-app 鸿蒙应用开发全流程解析
小白2023 低成本入局鸿蒙生态!Uniapp 适配鸿蒙实战分享,一次编码跑通多端
i***@alone88.cn 【鸿蒙征文】uni-app 鸿蒙开发实践:华为账号一键登录集成之路
GraceUI 【鸿蒙征文】关于鸿蒙折叠屏(宽屏设备)适配的一些分享
maq 鸿蒙企业应用内部分发打包教程
COOL团队 【鸿蒙征文】折腾鸿蒙分享功能的那些事儿
imseantang 【鸿蒙征文】从零到上架:用 uni-app 开发鸿蒙习惯养成应用习惯修仙的全流程实践
m***@163.com 一个人用 uni-app 做鸿蒙日语学习 App 的踩坑之旅
不如摸鱼去 uni-app 也能开发纯血鸿蒙 App?使用 wot-starter 这样快速上手!
陌上华年 【鸿蒙征文】UniApp(X) 让鸿蒙开发触手可及 —— LimeUI 组件库开发简录
tmui 【鸿蒙征文】记一次鸿蒙Next原生插件开发与Uniapp调用实战之弹层开发(tmui4x中的xToasts弹层)(含源码附件)
1***@qq.com 【鸿蒙征文】uniapp 实现鸿蒙自定义扫码界面
2***@qq.com 【鸿蒙征文】从鸿蒙适配到迎娶白富美:一个程序员的逆袭之路
VK168 【鸿蒙征文】uni-app鸿蒙上架必备技能:应用适配深色模式

三等奖

获奖作者 获奖文章
5***@qq.com 从uni-app到鸿蒙:爱影家影视App的跨平台开发实践
1***@qq.com 从Web到鸿蒙:uni-app x 开发踩坑与成长实录
诗酒趁闲 【鸿蒙征文】重拾儿时趣味的古诗 App 的开发过程记录
陌上华年 【鸿蒙征文】从零实现 uni-app 鸿蒙平台 TTS 插件:UTS 开发实践指南
知青 经验分享 鸿蒙里的权限设置,如何获取、查询权限
1***@qq.com 【鸿蒙征文】分享我 uniapp 集成鸿蒙企业微信的经验
暴走莫扎特 Uniapp 的鸿蒙 next 应用中隐藏和显示系统状态栏
正知名 【鸿蒙征文】从现在起,你的非原生弹窗"组件"们(自定义Toast、Modal等)只需要配置一次!
刘星 1024星光不负,码向未来——以 uni-app 筑梦鸿蒙生态我的uni-app鸿蒙开发之旅
青衫行者 【鸿蒙征文】星光不负,码向未来:uni-app + uniCloud 赋能社区管理系统的高效适配与生态融合实践
坚果派 摩尔斯电码转换器
小疯子呵 用uni-app搞了个足球战术板,踩了不少canvas坑,分享一下经验
VK168 【鸿蒙征文】uni-app 现有 UI 库兼容鸿蒙系统开发指南
3***@qq.com uni-app 打通鸿蒙从开发到上架:一条龙落地指南
程序媛夏天 使用 uni-app x 在 HarmonyOS 平台开发波斯历转换器的实践与思考
8***@qq.com 在 UniApp 中用 UTS 封装钉钉登录(HarmonyOS)
pickled 解决uniapp鸿蒙适配深色模式的问题
xxiaohe0601 【鸿蒙征文】Uni ECharts 2.1 发布:正式支持鸿蒙,零成本迁移、全平台兼容、跨端开发零负担!
Isaacedvr 鸿图初展,蒙学破茧:与uniapp-x和tmui4.0共闯鸿蒙Next实战录
Deminic 从零到一开发鸿蒙6原生时钟应用:uni-app x 完全实战指南
奇风2016 【鸿蒙征文】uniapp 赋能鸿蒙,应用开发速度翻倍,十天速通4个APP
i***@alone88.cn uni-app 鸿蒙应用开发实战:优雅解决文件下载存储路径问题
小疯子呵 【弧形导航栏】中间凸起按钮和消息未读角标,支持鸿蒙
8***@qq.com UniApp 项目鸿蒙上线
y***@163.com uniapp极速上手鸿蒙开发
8***@qq.com 鸿蒙 UTS 插件开发实战:屏幕方向控制插件的完整实现
用户2914143 使用 uni-app x 开发2048游戏适配鸿蒙6
威龙 【鸿蒙征文】使用 UTS 插件优雅实现"一键退出应用"功能
阿岳 【鸿蒙征文】使用 uni-app 开发鸿蒙 App,如何实现 H5 网页和 App 互通讯?
知青 鸿蒙 UTS 插件使用三方依赖、本地依赖

方向二特别奖

获奖作者 获奖文章
yuhespace 【鸿蒙征文】从创业小白到省赛获奖:我们用 uni-app 做出了"校园人人帮"
d***@qq.com 【鸿蒙征文】 炸裂!我用uni-app三天让旧应用通杀鸿蒙Next+元服务,华为商店已上架!2W奖励金即将到账。
imseantang 【鸿蒙征文】从零到上架:用 uni-app 开发鸿蒙习惯养成应用习惯修仙的全流程实践
iq2s 精膳通智慧食堂的鸿蒙开发之旅:因 uni-app 而简化,为国产生态而让利
clearliu 从痛点到产品:uni-app x + HarmonyOS打造房产投资管理系统全记录
5***@qq.com 从uni-app到鸿蒙:爱影家影视App的跨平台开发实践
m***@163.com 一个人用 uni-app 做鸿蒙日语学习 App 的踩坑之旅
8***@qq.com 集成鸿蒙五大核心能力,打造高性能生活服务类元服务
nutpi 从零到一:使用 uni-app x 开发鸿蒙 GitCode 目录树生成器
诗酒趁闲 【鸿蒙征文】重拾儿时趣味的古诗 App 的开发过程记录

奖项设置

一等奖(5名)
如上奖品为二选一,随机发放;

  • 华为手环7-NFC版
  • 华为智能水杯450ml + 露营灯-无级调光-太阳能+Type-C充电 组合

二等奖(15名)
露营灯-无级调光,太阳能 或 Type-C充电,随机发货

三等奖(30名)
HUAWEI 无线蓝牙鼠标-双模办公-灰色 (价值99元)

方向二特别奖(10名)
针对【方向二·案例实战】的优质投稿,额外设立10份 华为手环 9 NFC版作为激励!


奖品领取

请各位获奖作者尽快提交自己的邮寄地址,我们会陆续联系获奖人员发放奖品。

邮寄地址提交方式:登录 ask社区,点击右上角个人头像,进入设置界面,设置界面下方补充快递邮寄地址。


感谢所有参与本次征文活动的开发者,你们的分享让uni-app和鸿蒙生态更加繁荣!

收起阅读 »