UniApp开发者的噩梦终结!4.3a被拒的5个致命错误与避坑指南
亲爱的UniApp开发者们! 你是否经历过这样的场景:深夜加班,代码写得飞起,满怀期待地将应用提交到App Store审核,结果第二天醒来,邮件里赫然写着“4.3被拒”?😭 那一刻,感觉整个世界都崩塌了,对吧?别担心,你并不孤单!今天,我将为你揭开4.3被拒的神秘面纱,并分享5个致命错误及避坑指南,让你从此告别审核噩梦,轻松过审!🎉
引言:UniApp的魅力与挑战
UniApp作为一个跨平台开发框架,凭借其“一次编写,多端运行”的承诺,吸引了无数开发者。它允许你用Vue.js语法开发应用,同时生成iOS、Android、小程序等多个平台的代码,大大提升了开发效率。🚀 然而,这种跨平台特性也带来了独特的挑战,尤其是在iOS审核方面。苹果的App Store审核团队对应用质量有着极高的要求,任何不符合指南的行为都可能触发4.3拒绝,即“重复或类似应用”的拒绝。🍏
4.3被拒的核心在于苹果认为你的应用与市场上已有应用过于相似,缺乏创新或独特性。这不仅影响应用的上线时间,还可能损害开发者声誉。但别慌,通过深入分析常见错误并采取预防措施,我们可以有效规避这些问题。💡
致命错误1:过度依赖云打包,忽视本地打包潜力
错误描述:云打包的便捷陷阱
许多UniApp开发者习惯使用云打包服务,因为它简单快捷,无需配置复杂的本地环境。云打包服务由UniApp官方提供,开发者只需上传代码,平台自动处理编译和打包,生成iOS和Android应用包。📦 这种模式非常适合快速迭代和测试,但正是这种“便捷性”埋下了4.3被拒的隐患。
云打包的局限性在于,它无法让开发者深入控制编译过程。例如,iOS应用在云打包时,某些代码或资源可能被编译到动态库中,而开发者无法直接干预这些细节。这可能导致应用在审核时,因代码结构或功能实现与市场上其他应用相似而被拒。🔍
案例分析:代码结构相似性引发的4.3
假设你开发了一个健康管理应用,使用云打包服务。由于云打包的通用性,你的应用可能与其他健康应用在代码结构上高度相似,例如都使用了相同的第三方库或相同的UI组件。苹果审核团队在对比市场应用时,可能认为你的应用缺乏原创性,从而触发4.3拒绝。🏥
避坑指南:转向本地打包,掌握控制权
要避免这一问题,你需要从云打包转向本地打包。本地打包允许你直接操作Xcode工程,深入控制编译过程。以下是具体步骤:
下载UniApp本地打包指南:访问UniApp官方文档,找到“开发环境 | uni小程序SDK”部分,获取详细的本地打包配置说明。📚
重新配置Xcode工程:在Xcode中打开UniApp提供的工程模板,按照指南配置项目设置,如签名证书、应用标识符等。🔧
理解编译产物:使用工具如otool和size分析可执行文件,了解哪些代码被编译到主程序,哪些被放到动态库。这帮助你识别潜在的相似性风险。📊
自定义编译选项:在Xcode中调整编译设置,如优化代码混淆、删除不必要的第三方库,减少与市场应用的代码重叠。⚙️
通过本地打包,你不仅能避免4.3拒绝,还能提升应用性能,因为你可以优化资源加载和代码执行效率。💪
实际操作:从云到本的迁移步骤
备份云打包代码:在迁移前,确保备份所有云打包相关的代码和资源,以防数据丢失。💾
安装Xcode和依赖工具:确保你的开发环境安装了最新版本的Xcode和必要的命令行工具,如xcode-select。🍎
导入UniApp工程:将UniApp的本地打包工程导入Xcode,检查并修复任何编译错误。📥
测试和验证:在iOS模拟器或设备上测试应用,确保所有功能正常运行,尤其是那些在云打包中可能被忽略的细节。📱
致命错误2:忽视平台差异,代码“一锅炖”
错误描述:跨平台开发的“通用”陷阱
UniApp的跨平台特性鼓励开发者编写通用代码,但这也可能导致应用在不同平台上表现不佳,尤其在iOS上。例如,开发者可能使用JavaScript的window对象或特定Web API,这些在iOS的WebView中可能无法正常工作,或在审核时暴露平台差异,引发4.3拒绝。🌐
案例分析:平台特定API的误用
考虑一个登录页面,开发者使用了window.location.href进行页面跳转。在iOS的WebView中,此行为可能与原生应用导航不兼容,导致用户体验断裂。苹果审核团队可能认为应用缺乏平台一致性,从而拒绝应用。🔗
避坑指南:利用条件编译,精准适配
UniApp提供了条件编译指令,允许你为不同平台编写特定代码。以下是具体方法:
使用条件编译指令:在代码中使用#ifdef、#ifndef等指令,根据平台(iOS、Android等)编写特定逻辑。例如:
javascript
Copy Code
// #ifdef APP-PLUS
// iOS特定的代码
// #endif
平台特定API替换:避免使用跨平台不兼容的API,如window对象。改用UniApp提供的跨平台API,如uni.navigateTo进行页面跳转。📲
测试多平台兼容性:在开发过程中,频繁在iOS和Android模拟器上测试应用,确保所有功能在不同平台表现一致。🔍
实际操作:条件编译示例
创建平台特定页面:在UniApp项目中,为iOS和Android创建不同版本的页面,使用条件编译指令控制加载。例如:
vue
Copy Code
<template>
<view>
<!-- #ifdef APP-PLUS -->
<iOS-specific-component />
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<Android-specific-component />
<!-- #endif -->
</view>
</template>
使用UniApp API:在JavaScript逻辑中,优先使用UniApp的跨平台API,如uni.request替代原生XMLHttpRequest,确保行为一致性。📡
致命错误3:误用第三方插件,引入代码冲突
错误描述:第三方插件的兼容性风险
UniApp生态中的第三方插件提供了丰富功能,但它们的兼容性和代码质量参差不齐。某些插件可能包含与iOS审核指南冲突的代码,如使用私有API或实现类似市场应用的功能,导致4.3拒绝。🔌
案例分析:插件引发的功能相似性
假设你集成了一个广告插件,该插件在iOS上使用了非公开的广告SDK,或广告展示方式与市场应用高度相似。苹果审核团队可能认为应用缺乏创新,从而拒绝应用。📺
避坑指南:插件审核与替代方案
插件审核:在集成前,使用工具如otool分析插件生成的代码,检查是否有私有API或敏感操作。📝
选择高质量插件:优先选择官方推荐或社区评价高的插件,避免使用来源不明的插件。🌟
自定义功能实现:对于关键功能,考虑自行实现而非依赖插件,减少代码相似性风险。💡
实际操作:插件集成步骤
插件市场调研:在UniApp插件市场浏览插件,阅读用户评价和更新日志,选择活跃维护的插件。🛒
代码分析:集成插件后,在Xcode中分析编译产物,使用nm命令检查符号表,确保无私有API引用。🔬
测试广告功能:在iOS设备上测试广告展示,确保广告策略(如位置、频率)符合苹果指南,避免与市场应用雷同。📊
致命错误4:忽视元数据优化,信息不准确
错误描述:元数据与应用的“身份”错位
应用元数据(如应用名称、描述、图标)是审核团队的第一印象。不准确或误导性的元数据可能触发4.3拒绝,因为苹果认为应用与描述不符,缺乏独特性。📝
案例分析:元数据不匹配引发的拒绝
假设你的应用名称是“健康助手”,但实际功能仅限于计步,而市场上有许多类似应用。苹果可能认为名称误导用户,或应用功能与市场应用重叠,从而拒绝应用。🏃
避坑指南:元数据优化策略
名称独特性:选择不常见的名称,避免使用通用词汇,如“工具”、“助手”。🎨
描述准确性:在应用描述中明确核心功能,避免夸大或模糊表述。📖
图标创新:设计独特图标,避免与市场应用雷同,使用工具如Canva创建原创设计。🖼️
实际操作:元数据优化步骤
市场调研:在App Store搜索关键词,分析竞品元数据,确保你的应用名称和描述独树一帜。🔍
描述撰写:编写清晰、简洁的应用描述,突出核心功能和用户价值,避免技术术语堆砌。✍️
图标设计:使用设计工具创建图标,确保颜色、形状和风格与竞品区分,提交前进行多设备预览。📱
致命错误5:代码混淆不足,暴露核心逻辑
错误描述:代码混淆的“透明”风险
代码混淆是保护应用核心逻辑不被逆向工程的关键。未混淆的代码可能在审核时被苹果团队分析,暴露与市场应用的相似性,触发4.3拒绝。🔐
案例分析:代码暴露引发的拒绝
假设你的应用使用了未混淆的JavaScript代码,包含通用算法或UI逻辑。苹果审核团队可能通过代码分析,认为应用功能与市场应用相似,从而拒绝应用。📜
避坑指南:代码混淆与加固
使用ProGuard(Android)和R8(iOS):在本地打包配置中启用代码混淆工具,删除无用代码、重命名类和变量。🛡️
自定义混淆规则:设置混淆规则,保护核心算法和业务逻辑,避免过度混淆导致应用崩溃。⚙️
测试混淆效果:在混淆后,在模拟器和设备上测试应用,确保功能正常运行,监控性能影响。📊
实际操作:代码混淆步骤
配置混淆工具:在UniApp的本地打包工程中,找到混淆配置选项,启用ProGuard或R8。🔧
设置规则文件:创建自定义混淆规则文件,指定需要保护的类和变量,避免过度混淆。📝
测试与验证:混淆后,执行全面测试,包括功能测试和性能测试,确保应用稳定。✅
总结与行动号召
亲爱的UniApp开发者,通过深入分析这5个致命错误,你现在掌握了避免4.3被拒的关键策略。从云打包转向本地打包,精准适配平台差异,谨慎选择第三方插件,优化元数据,以及强化代码混淆,这些步骤将显著提升你的应用过审概率。🚀
立即行动,告别审核噩梦! 如果你觉得这篇指南有价值,请转发给其他UniApp开发者,帮助他们避免同样的陷阱。💌 同时,欢迎在评论区分享你的审核故事或提问,让我们一起在UniApp开发的道路上越走越顺!🎉
像淘宝客、盲盒、商城、聊天、AI翻译之类的项目,我们都有解决案例,2天解决uni项目被拒问题
如需提供技术支持,可以联系微信:anli68036
亲爱的UniApp开发者们! 你是否经历过这样的场景:深夜加班,代码写得飞起,满怀期待地将应用提交到App Store审核,结果第二天醒来,邮件里赫然写着“4.3被拒”?😭 那一刻,感觉整个世界都崩塌了,对吧?别担心,你并不孤单!今天,我将为你揭开4.3被拒的神秘面纱,并分享5个致命错误及避坑指南,让你从此告别审核噩梦,轻松过审!🎉
引言:UniApp的魅力与挑战
UniApp作为一个跨平台开发框架,凭借其“一次编写,多端运行”的承诺,吸引了无数开发者。它允许你用Vue.js语法开发应用,同时生成iOS、Android、小程序等多个平台的代码,大大提升了开发效率。🚀 然而,这种跨平台特性也带来了独特的挑战,尤其是在iOS审核方面。苹果的App Store审核团队对应用质量有着极高的要求,任何不符合指南的行为都可能触发4.3拒绝,即“重复或类似应用”的拒绝。🍏
4.3被拒的核心在于苹果认为你的应用与市场上已有应用过于相似,缺乏创新或独特性。这不仅影响应用的上线时间,还可能损害开发者声誉。但别慌,通过深入分析常见错误并采取预防措施,我们可以有效规避这些问题。💡
致命错误1:过度依赖云打包,忽视本地打包潜力
错误描述:云打包的便捷陷阱
许多UniApp开发者习惯使用云打包服务,因为它简单快捷,无需配置复杂的本地环境。云打包服务由UniApp官方提供,开发者只需上传代码,平台自动处理编译和打包,生成iOS和Android应用包。📦 这种模式非常适合快速迭代和测试,但正是这种“便捷性”埋下了4.3被拒的隐患。
云打包的局限性在于,它无法让开发者深入控制编译过程。例如,iOS应用在云打包时,某些代码或资源可能被编译到动态库中,而开发者无法直接干预这些细节。这可能导致应用在审核时,因代码结构或功能实现与市场上其他应用相似而被拒。🔍
案例分析:代码结构相似性引发的4.3
假设你开发了一个健康管理应用,使用云打包服务。由于云打包的通用性,你的应用可能与其他健康应用在代码结构上高度相似,例如都使用了相同的第三方库或相同的UI组件。苹果审核团队在对比市场应用时,可能认为你的应用缺乏原创性,从而触发4.3拒绝。🏥
避坑指南:转向本地打包,掌握控制权
要避免这一问题,你需要从云打包转向本地打包。本地打包允许你直接操作Xcode工程,深入控制编译过程。以下是具体步骤:
下载UniApp本地打包指南:访问UniApp官方文档,找到“开发环境 | uni小程序SDK”部分,获取详细的本地打包配置说明。📚
重新配置Xcode工程:在Xcode中打开UniApp提供的工程模板,按照指南配置项目设置,如签名证书、应用标识符等。🔧
理解编译产物:使用工具如otool和size分析可执行文件,了解哪些代码被编译到主程序,哪些被放到动态库。这帮助你识别潜在的相似性风险。📊
自定义编译选项:在Xcode中调整编译设置,如优化代码混淆、删除不必要的第三方库,减少与市场应用的代码重叠。⚙️
通过本地打包,你不仅能避免4.3拒绝,还能提升应用性能,因为你可以优化资源加载和代码执行效率。💪
实际操作:从云到本的迁移步骤
备份云打包代码:在迁移前,确保备份所有云打包相关的代码和资源,以防数据丢失。💾
安装Xcode和依赖工具:确保你的开发环境安装了最新版本的Xcode和必要的命令行工具,如xcode-select。🍎
导入UniApp工程:将UniApp的本地打包工程导入Xcode,检查并修复任何编译错误。📥
测试和验证:在iOS模拟器或设备上测试应用,确保所有功能正常运行,尤其是那些在云打包中可能被忽略的细节。📱
致命错误2:忽视平台差异,代码“一锅炖”
错误描述:跨平台开发的“通用”陷阱
UniApp的跨平台特性鼓励开发者编写通用代码,但这也可能导致应用在不同平台上表现不佳,尤其在iOS上。例如,开发者可能使用JavaScript的window对象或特定Web API,这些在iOS的WebView中可能无法正常工作,或在审核时暴露平台差异,引发4.3拒绝。🌐
案例分析:平台特定API的误用
考虑一个登录页面,开发者使用了window.location.href进行页面跳转。在iOS的WebView中,此行为可能与原生应用导航不兼容,导致用户体验断裂。苹果审核团队可能认为应用缺乏平台一致性,从而拒绝应用。🔗
避坑指南:利用条件编译,精准适配
UniApp提供了条件编译指令,允许你为不同平台编写特定代码。以下是具体方法:
使用条件编译指令:在代码中使用#ifdef、#ifndef等指令,根据平台(iOS、Android等)编写特定逻辑。例如:
javascript
Copy Code
// #ifdef APP-PLUS
// iOS特定的代码
// #endif
平台特定API替换:避免使用跨平台不兼容的API,如window对象。改用UniApp提供的跨平台API,如uni.navigateTo进行页面跳转。📲
测试多平台兼容性:在开发过程中,频繁在iOS和Android模拟器上测试应用,确保所有功能在不同平台表现一致。🔍
实际操作:条件编译示例
创建平台特定页面:在UniApp项目中,为iOS和Android创建不同版本的页面,使用条件编译指令控制加载。例如:
vue
Copy Code
<template>
<view>
<!-- #ifdef APP-PLUS -->
<iOS-specific-component />
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<Android-specific-component />
<!-- #endif -->
</view>
</template>
使用UniApp API:在JavaScript逻辑中,优先使用UniApp的跨平台API,如uni.request替代原生XMLHttpRequest,确保行为一致性。📡
致命错误3:误用第三方插件,引入代码冲突
错误描述:第三方插件的兼容性风险
UniApp生态中的第三方插件提供了丰富功能,但它们的兼容性和代码质量参差不齐。某些插件可能包含与iOS审核指南冲突的代码,如使用私有API或实现类似市场应用的功能,导致4.3拒绝。🔌
案例分析:插件引发的功能相似性
假设你集成了一个广告插件,该插件在iOS上使用了非公开的广告SDK,或广告展示方式与市场应用高度相似。苹果审核团队可能认为应用缺乏创新,从而拒绝应用。📺
避坑指南:插件审核与替代方案
插件审核:在集成前,使用工具如otool分析插件生成的代码,检查是否有私有API或敏感操作。📝
选择高质量插件:优先选择官方推荐或社区评价高的插件,避免使用来源不明的插件。🌟
自定义功能实现:对于关键功能,考虑自行实现而非依赖插件,减少代码相似性风险。💡
实际操作:插件集成步骤
插件市场调研:在UniApp插件市场浏览插件,阅读用户评价和更新日志,选择活跃维护的插件。🛒
代码分析:集成插件后,在Xcode中分析编译产物,使用nm命令检查符号表,确保无私有API引用。🔬
测试广告功能:在iOS设备上测试广告展示,确保广告策略(如位置、频率)符合苹果指南,避免与市场应用雷同。📊
致命错误4:忽视元数据优化,信息不准确
错误描述:元数据与应用的“身份”错位
应用元数据(如应用名称、描述、图标)是审核团队的第一印象。不准确或误导性的元数据可能触发4.3拒绝,因为苹果认为应用与描述不符,缺乏独特性。📝
案例分析:元数据不匹配引发的拒绝
假设你的应用名称是“健康助手”,但实际功能仅限于计步,而市场上有许多类似应用。苹果可能认为名称误导用户,或应用功能与市场应用重叠,从而拒绝应用。🏃
避坑指南:元数据优化策略
名称独特性:选择不常见的名称,避免使用通用词汇,如“工具”、“助手”。🎨
描述准确性:在应用描述中明确核心功能,避免夸大或模糊表述。📖
图标创新:设计独特图标,避免与市场应用雷同,使用工具如Canva创建原创设计。🖼️
实际操作:元数据优化步骤
市场调研:在App Store搜索关键词,分析竞品元数据,确保你的应用名称和描述独树一帜。🔍
描述撰写:编写清晰、简洁的应用描述,突出核心功能和用户价值,避免技术术语堆砌。✍️
图标设计:使用设计工具创建图标,确保颜色、形状和风格与竞品区分,提交前进行多设备预览。📱
致命错误5:代码混淆不足,暴露核心逻辑
错误描述:代码混淆的“透明”风险
代码混淆是保护应用核心逻辑不被逆向工程的关键。未混淆的代码可能在审核时被苹果团队分析,暴露与市场应用的相似性,触发4.3拒绝。🔐
案例分析:代码暴露引发的拒绝
假设你的应用使用了未混淆的JavaScript代码,包含通用算法或UI逻辑。苹果审核团队可能通过代码分析,认为应用功能与市场应用相似,从而拒绝应用。📜
避坑指南:代码混淆与加固
使用ProGuard(Android)和R8(iOS):在本地打包配置中启用代码混淆工具,删除无用代码、重命名类和变量。🛡️
自定义混淆规则:设置混淆规则,保护核心算法和业务逻辑,避免过度混淆导致应用崩溃。⚙️
测试混淆效果:在混淆后,在模拟器和设备上测试应用,确保功能正常运行,监控性能影响。📊
实际操作:代码混淆步骤
配置混淆工具:在UniApp的本地打包工程中,找到混淆配置选项,启用ProGuard或R8。🔧
设置规则文件:创建自定义混淆规则文件,指定需要保护的类和变量,避免过度混淆。📝
测试与验证:混淆后,执行全面测试,包括功能测试和性能测试,确保应用稳定。✅
总结与行动号召
亲爱的UniApp开发者,通过深入分析这5个致命错误,你现在掌握了避免4.3被拒的关键策略。从云打包转向本地打包,精准适配平台差异,谨慎选择第三方插件,优化元数据,以及强化代码混淆,这些步骤将显著提升你的应用过审概率。🚀
立即行动,告别审核噩梦! 如果你觉得这篇指南有价值,请转发给其他UniApp开发者,帮助他们避免同样的陷阱。💌 同时,欢迎在评论区分享你的审核故事或提问,让我们一起在UniApp开发的道路上越走越顺!🎉
像淘宝客、盲盒、商城、聊天、AI翻译之类的项目,我们都有解决案例,2天解决uni项目被拒问题
如需提供技术支持,可以联系微信:anli68036
收起阅读 »uniapp专用ipa上传工具苹果ios上架审核提包ipa防关联windows版
# iOSUploader
ipa ios review审核上架提包Transporter for window 防关联
1.最新下载地址
ipa Transporter for window - iOSUploader
2.首次打开软`件报错,需要安装微软官方.NET 8.0 Core下载地址:
https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/8.0.22/windowsdesktop-runtime-8.0.22-win-x64.exe
3.打开文件夹里iOSUploader.exe主程序就可以使用了
4.配置IssuerID、密钥KeyID、p8格式的证书路径
具体获取地址苹果官方入口,https://appstoreconnect.apple.com/access/integrations/api
登录你的苹果开发者id和密码,【顶部集成】->【左侧的App Store Connect API】->【右侧团队密钥加号】就有了,p8文件只能下载一次,注意保存好;
比市面上Appleid和密码登录方式更加安全,不泄露密码,也防止了一台电脑多个appleid账户登录的问题
5. ipa上传到AppStore Connect中心准备提交审核
第一步:点击选择ipa按钮,选择打包签名好的ipa文件,然后程序会自动解析出来AppStoreConnect管理里的App ID
第二步:点击上传IPA按钮,进度条走到头就上传成功了。
6. 证书管理
可以创建证书,包括开发develope、发布上架distribute证书,证书右侧点击管理栏目,可以下载或者撤销当前证书
输入Email创建证书
7. 包名BundleIdentifier管理
点击【左下角创建Bundle】、每行的【管理】按钮可以弹出来创建更新框
8. 设备DeviceID管理
快速录入需要调试开发的苹果设备deviceid
查看每个设备deviceid
9. 描述文件mobileprovision管理
轻松快速新建和下载打包需要的描述文件
快速创建描述文件,选择包名、证书、设备udid,
10. AppStoreConnect的App管理
在这里可以快速的查看当前苹果开发者账号下的所有app和详情信息,
快速批量上传app对应每种语言的多种尺寸预览图,快捷实用
快速查看、更新app的基本信息,包括版本号、版权信息、内容版权声明、主要语言等
本地化管理,快速查看更新多语言下的app详细信息,
包括每种语言的,app名称、副标题
描述、关键词、更新内容、营销URL、隐私政策URL、技术支持URL等信息
快速更新查看app的分类
快速更新查看当前app的销售价格
快速查看更新app的销售国家和地区
快速更新查看app的年龄管理
# iOSUploader
ipa ios review审核上架提包Transporter for window 防关联
1.最新下载地址
ipa Transporter for window - iOSUploader
2.首次打开软`件报错,需要安装微软官方.NET 8.0 Core下载地址:
https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/8.0.22/windowsdesktop-runtime-8.0.22-win-x64.exe
3.打开文件夹里iOSUploader.exe主程序就可以使用了
4.配置IssuerID、密钥KeyID、p8格式的证书路径
具体获取地址苹果官方入口,https://appstoreconnect.apple.com/access/integrations/api
登录你的苹果开发者id和密码,【顶部集成】->【左侧的App Store Connect API】->【右侧团队密钥加号】就有了,p8文件只能下载一次,注意保存好;
比市面上Appleid和密码登录方式更加安全,不泄露密码,也防止了一台电脑多个appleid账户登录的问题
5. ipa上传到AppStore Connect中心准备提交审核
第一步:点击选择ipa按钮,选择打包签名好的ipa文件,然后程序会自动解析出来AppStoreConnect管理里的App ID
第二步:点击上传IPA按钮,进度条走到头就上传成功了。
6. 证书管理
可以创建证书,包括开发develope、发布上架distribute证书,证书右侧点击管理栏目,可以下载或者撤销当前证书
输入Email创建证书
7. 包名BundleIdentifier管理
点击【左下角创建Bundle】、每行的【管理】按钮可以弹出来创建更新框
8. 设备DeviceID管理
快速录入需要调试开发的苹果设备deviceid
查看每个设备deviceid
9. 描述文件mobileprovision管理
轻松快速新建和下载打包需要的描述文件
快速创建描述文件,选择包名、证书、设备udid,
10. AppStoreConnect的App管理
在这里可以快速的查看当前苹果开发者账号下的所有app和详情信息,
快速批量上传app对应每种语言的多种尺寸预览图,快捷实用
快速查看、更新app的基本信息,包括版本号、版权信息、内容版权声明、主要语言等
本地化管理,快速查看更新多语言下的app详细信息,
包括每种语言的,app名称、副标题
描述、关键词、更新内容、营销URL、隐私政策URL、技术支持URL等信息
快速更新查看app的分类
快速更新查看当前app的销售价格
快速查看更新app的销售国家和地区
快速更新查看app的年龄管理
uniapp专用ipa上传工具苹果ios上架审核提包ipa防关联windows版
iOSUploader
ipa ios review审核上架提包Transporter for window 防关联
1.最新下载地址
ipa Transporter for window - iOSUploader
<img width="1419" height="864" alt="1 - 副本" src="https://iosuploader.xinxishehui.com/images/1.png" />
2.首次打开软件报错,需要安装微软官方.NET 8.0 Core下载地址:
https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/8.0.22/windowsdesktop-runtime-8.0.22-win-x64.exe
<img width="692" height="270" alt="fc9a38da-4496-4d00-a8bd-371b568a5f0f" src="https://github.com/user-attachments/assets/922c6847-9ead-4fa4-944c-a3e7b6635489" />
3.打开文件夹里iOSUploader.exe主程序就可以使用了
<img width="692" height="473" alt="749a4c43-e632-4d71-87da-05e5bef2b763" src="https://github.com/user-attachments/assets/52d380ed-ee28-453a-bb19-f5dc724d943c" />
4.配置IssuerID、密钥KeyID、p8格式的证书路径
具体获取地址苹果官方入口,https://appstoreconnect.apple.com/access/integrations/api
登录你的苹果开发者id和密码,【顶部集成】->【左侧的App Store Connect API】->【右侧团队密钥加号】就有了,p8文件只能下载一次,注意保存好;
比市面上Appleid和密码登录方式更加安全,不泄露密码,也防止了一台电脑多个appleid账户登录的问题
<img width="691" height="492" alt="78ca9f8f-6e4b-41ac-b39a-35e924826d64" src="https://github.com/user-attachments/assets/1d094331-eae9-46d5-b595-669a41302f2a" />
<img width="1421" height="865" alt="7" src="https://github.com/user-attachments/assets/38b335f3-2bc3-4d67-9202-5aa61819755c" />
5. ipa上传到AppStore Connect中心准备提交审核
第一步:点击选择ipa按钮,选择打包签名好的ipa文件,然后程序会自动解析出来AppStoreConnect管理里的App ID
第二步:点击上传IPA按钮,进度条走到头就上传成功了。
<img width="1419" height="864" alt="1" src="https://github.com/user-attachments/assets/5809c2eb-0361-4e94-a520-06b9a6c9a645" />
6. 证书管理
可以创建证书,包括开发develope、发布上架distribute证书,证书右侧点击管理栏目,可以下载或者撤销当前证书
<img width="1411" height="864" alt="3" src="https://github.com/user-attachments/assets/1ca9f070-843b-48c1-9c31-905547a95428" />
输入Email创建证书
<img width="692" height="418" alt="81a8692c-9b2e-4250-beed-0117d61719c3" src="https://github.com/user-attachments/assets/34f0afd4-519c-4aae-ba90-80f56c272d57" />
7. 包名BundleIdentifier管理
<img width="1419" height="860" alt="4" src="https://github.com/user-attachments/assets/e1495a70-38bf-40d3-9019-0b639ee854f6" />
点击【左下角创建Bundle】、每行的【管理】按钮可以弹出来创建更新框
8. 设备DeviceID管理
快速录入需要调试开发的苹果设备deviceid
<img width="1420" height="865" alt="5" src="https://github.com/user-attachments/assets/24da0696-d614-4477-ac8a-b1503f46cebe" />
查看每个设备deviceid
9. 描述文件mobileprovision管理
轻松快速新建和下载打包需要的描述文件
<img width="1421" height="868" alt="6" src="https://github.com/user-attachments/assets/0635accc-a38f-47e0-887f-63f731b248f3" />
快速创建描述文件,选择包名、证书、设备udid,
<img width="1219" height="895" alt="8" src="https://github.com/user-attachments/assets/1c4f5d05-a893-4083-ad72-f2763a06cde6" />
10. AppStoreConnect的App管理
在这里可以快速的查看当前苹果开发者账号下的所有app和详情信息,
<img width="1420" height="860" alt="2" src="https://github.com/user-attachments/assets/f515b9fe-18bb-4e8b-97dc-4a06bca153f1" />
快速批量上传app对应每种语言的多种尺寸预览图,快捷实用
<img width="691" height="314" alt="60944c9f-a68b-4778-bec9-bf13a22189ff" src="https://github.com/user-attachments/assets/e33f8d33-d5c0-4ec7-9ae5-56be849c3075" />
快速查看、更新app的基本信息,包括版本号、版权信息、内容版权声明、主要语言等
<img width="692" height="326" alt="202b9b80-ffaf-49ca-ae98-2f5beb512100" src="https://github.com/user-attachments/assets/40e31cdb-e3a5-45f6-bcab-df2d54440bd1" />
本地化管理,快速查看更新多语言下的app详细信息,
包括每种语言的,app名称、副标题
描述、关键词、更新内容、营销URL、隐私政策URL、技术支持URL等信息
<img width="748" height="319" alt="cabf2fd8-a5f9-43ac-b3f6-05ee5a68d0c1" src="https://github.com/user-attachments/assets/bb3c67cb-45b6-494e-bf9b-6154765b4b25" />
快速更新查看app的分类
<img width="693" height="389" alt="f51f9948-ec71-4040-af87-927cca1bbb7c" src="https://github.com/user-attachments/assets/ae70e3cc-52b8-43c1-a58d-a45b09b31d7d" />
快速更新查看当前app的销售价格
<img width="691" height="388" alt="5c18e5f3-c78e-40c2-bd9a-fd86de8df0e8" src="https://github.com/user-attachments/assets/82ad1cc6-b3ef-442c-aaca-e472ebe8e1ba" />
快速查看更新app的销售国家和地区
<img width="693" height="337" alt="5e336bc4-dbf4-4b48-8da5-1b2432c79da8" src="https://github.com/user-attachments/assets/d6429872-9dbf-4145-af56-872ef38ab7bb" />
快速更新查看app的年龄管理
<img width="691" height="358" alt="955e08f0-28eb-4e9f-a1c3-7f531616e9a9" src="https://github.com/user-attachments/assets/f61c8c90-6f74-47ce-9f6d-b47e0dcbfd1e" />
iOSUploader
ipa ios review审核上架提包Transporter for window 防关联
1.最新下载地址
ipa Transporter for window - iOSUploader
<img width="1419" height="864" alt="1 - 副本" src="https://iosuploader.xinxishehui.com/images/1.png" />
2.首次打开软件报错,需要安装微软官方.NET 8.0 Core下载地址:
https://builds.dotnet.microsoft.com/dotnet/WindowsDesktop/8.0.22/windowsdesktop-runtime-8.0.22-win-x64.exe
<img width="692" height="270" alt="fc9a38da-4496-4d00-a8bd-371b568a5f0f" src="https://github.com/user-attachments/assets/922c6847-9ead-4fa4-944c-a3e7b6635489" />
3.打开文件夹里iOSUploader.exe主程序就可以使用了
<img width="692" height="473" alt="749a4c43-e632-4d71-87da-05e5bef2b763" src="https://github.com/user-attachments/assets/52d380ed-ee28-453a-bb19-f5dc724d943c" />
4.配置IssuerID、密钥KeyID、p8格式的证书路径
具体获取地址苹果官方入口,https://appstoreconnect.apple.com/access/integrations/api
登录你的苹果开发者id和密码,【顶部集成】->【左侧的App Store Connect API】->【右侧团队密钥加号】就有了,p8文件只能下载一次,注意保存好;
比市面上Appleid和密码登录方式更加安全,不泄露密码,也防止了一台电脑多个appleid账户登录的问题
<img width="691" height="492" alt="78ca9f8f-6e4b-41ac-b39a-35e924826d64" src="https://github.com/user-attachments/assets/1d094331-eae9-46d5-b595-669a41302f2a" />
<img width="1421" height="865" alt="7" src="https://github.com/user-attachments/assets/38b335f3-2bc3-4d67-9202-5aa61819755c" />
5. ipa上传到AppStore Connect中心准备提交审核
第一步:点击选择ipa按钮,选择打包签名好的ipa文件,然后程序会自动解析出来AppStoreConnect管理里的App ID
第二步:点击上传IPA按钮,进度条走到头就上传成功了。
<img width="1419" height="864" alt="1" src="https://github.com/user-attachments/assets/5809c2eb-0361-4e94-a520-06b9a6c9a645" />
6. 证书管理
可以创建证书,包括开发develope、发布上架distribute证书,证书右侧点击管理栏目,可以下载或者撤销当前证书
<img width="1411" height="864" alt="3" src="https://github.com/user-attachments/assets/1ca9f070-843b-48c1-9c31-905547a95428" />
输入Email创建证书
<img width="692" height="418" alt="81a8692c-9b2e-4250-beed-0117d61719c3" src="https://github.com/user-attachments/assets/34f0afd4-519c-4aae-ba90-80f56c272d57" />
7. 包名BundleIdentifier管理
<img width="1419" height="860" alt="4" src="https://github.com/user-attachments/assets/e1495a70-38bf-40d3-9019-0b639ee854f6" />
点击【左下角创建Bundle】、每行的【管理】按钮可以弹出来创建更新框
8. 设备DeviceID管理
快速录入需要调试开发的苹果设备deviceid
<img width="1420" height="865" alt="5" src="https://github.com/user-attachments/assets/24da0696-d614-4477-ac8a-b1503f46cebe" />
查看每个设备deviceid
9. 描述文件mobileprovision管理
轻松快速新建和下载打包需要的描述文件
<img width="1421" height="868" alt="6" src="https://github.com/user-attachments/assets/0635accc-a38f-47e0-887f-63f731b248f3" />
快速创建描述文件,选择包名、证书、设备udid,
<img width="1219" height="895" alt="8" src="https://github.com/user-attachments/assets/1c4f5d05-a893-4083-ad72-f2763a06cde6" />
10. AppStoreConnect的App管理
在这里可以快速的查看当前苹果开发者账号下的所有app和详情信息,
<img width="1420" height="860" alt="2" src="https://github.com/user-attachments/assets/f515b9fe-18bb-4e8b-97dc-4a06bca153f1" />
快速批量上传app对应每种语言的多种尺寸预览图,快捷实用
<img width="691" height="314" alt="60944c9f-a68b-4778-bec9-bf13a22189ff" src="https://github.com/user-attachments/assets/e33f8d33-d5c0-4ec7-9ae5-56be849c3075" />
快速查看、更新app的基本信息,包括版本号、版权信息、内容版权声明、主要语言等
<img width="692" height="326" alt="202b9b80-ffaf-49ca-ae98-2f5beb512100" src="https://github.com/user-attachments/assets/40e31cdb-e3a5-45f6-bcab-df2d54440bd1" />
本地化管理,快速查看更新多语言下的app详细信息,
包括每种语言的,app名称、副标题
描述、关键词、更新内容、营销URL、隐私政策URL、技术支持URL等信息
<img width="748" height="319" alt="cabf2fd8-a5f9-43ac-b3f6-05ee5a68d0c1" src="https://github.com/user-attachments/assets/bb3c67cb-45b6-494e-bf9b-6154765b4b25" />
快速更新查看app的分类
<img width="693" height="389" alt="f51f9948-ec71-4040-af87-927cca1bbb7c" src="https://github.com/user-attachments/assets/ae70e3cc-52b8-43c1-a58d-a45b09b31d7d" />
快速更新查看当前app的销售价格
<img width="691" height="388" alt="5c18e5f3-c78e-40c2-bd9a-fd86de8df0e8" src="https://github.com/user-attachments/assets/82ad1cc6-b3ef-442c-aaca-e472ebe8e1ba" />
快速查看更新app的销售国家和地区
<img width="693" height="337" alt="5e336bc4-dbf4-4b48-8da5-1b2432c79da8" src="https://github.com/user-attachments/assets/d6429872-9dbf-4145-af56-872ef38ab7bb" />
快速更新查看app的年龄管理
<img width="691" height="358" alt="955e08f0-28eb-4e9f-a1c3-7f531616e9a9" src="https://github.com/user-attachments/assets/f61c8c90-6f74-47ce-9f6d-b47e0dcbfd1e" />
收起阅读 »手持GPS终端:获取系统定位出错:get location fail
安卓os系统版本 android 7
uni.getLocation({
type: 'wgs84',
success: (res) => {
console.log('当前位置:', res);
uni.showModal({
title: '提示成功',
content: 纬度: ${res.latitude}, 经度: ${res.longitude}, 精度: ${res.accuracy}m,
showCancel: false
});
this.handleLocationChange(res)
},
fail: (err) => {
uni.showModal({
title: '提示失败',
content: 错误信息: ${err.errMsg || JSON.stringify(err)},
showCancel: false
});
console.error('获取当前位置失败:', err);
}
}); 安卓os系统版本 android 7
uni.getLocation({
type: 'wgs84',
success: (res) => {
console.log('当前位置:', res);
uni.showModal({
title: '提示成功',
content: 纬度: ${res.latitude}, 经度: ${res.longitude}, 精度: ${res.accuracy}m,
showCancel: false
});
this.handleLocationChange(res)
},
fail: (err) => {
uni.showModal({
title: '提示失败',
content: 错误信息: ${err.errMsg || JSON.stringify(err)},
showCancel: false
});
console.error('获取当前位置失败:', err);
}
}); 收起阅读 »
manifest.json配置很怪,体验很差
manifest.json中有 app模块的配置面版,也有源码视图,也就是存在两个地方可以配置,但是这两个地方数据并不同步;
比如我在安卓/iOS常用其他配置中配置了支持CPU类型,勾选了其中两个,但是在源码视图中并没有自动生成对应的key-value, 而在源码视图中我又可以自由编辑同时也不会同步到配置面版中,如何这两个地方配置的不一样,以哪个为准??
很多东西感觉就是可以用,功能有,但是用起来并不顺手, 以上为例:哪怕你把配置面版删掉,只留源码配置,整个流程就会清晰很多
manifest.json中有 app模块的配置面版,也有源码视图,也就是存在两个地方可以配置,但是这两个地方数据并不同步;
比如我在安卓/iOS常用其他配置中配置了支持CPU类型,勾选了其中两个,但是在源码视图中并没有自动生成对应的key-value, 而在源码视图中我又可以自由编辑同时也不会同步到配置面版中,如何这两个地方配置的不一样,以哪个为准??
很多东西感觉就是可以用,功能有,但是用起来并不顺手, 以上为例:哪怕你把配置面版删掉,只留源码配置,整个流程就会清晰很多
APP本地 Android 原生工程,并且原生工程里面有 Android 原生模块,如何发版?是用 Android Studio 直接发版还是用 HBuilderX 发版?
问题: ‘APP本地 Android 原生工程,并且原生工程里面有 Android 原生模块,如何发版?是用 Android Studio 直接发版还是用 HBuilderX 发版?’
答案:
需补充hbuilderx版本信息及具体发版需求细节。
是的,即使是原生工程,必须将Android原生模块封装成uni-app原生插件才能通过HBuilderX正常发版上线。直接使用Android Studio打包的APK不符合uni-app发布规范,无法正确集成原生模块。
关键流程:
- 开发原生模块:在Android Studio中完成开发调试,参考Android平台uni原生插件开发指南。
- 插件封装:按uni原生插件包格式要求打包为ZIP(根目录为插件ID,包含
android目录和package.json),放入项目nativeplugins目录。 - 正式发版:必须通过HBuilderX操作:
- 选择"发行" → "云打包",上传插件并配置参数。
- 不可勾选"自定义基座"(仅用于调试),需重新提交云端打包生成正式版APK。
- 直接使用Android Studio打包的APK无法通过应用商店审核。
原因说明:
- uni-app的正式版依赖云端打包机制统一处理插件兼容性、签名及64位支持(如Android商店审核要求)。
- 自定义基座仅用于本地调试,不能作为正式版发布(见云端插件打包发布指南)。
务必遵循此流程,否则原生模块无法生效且无法上架应用商店。
问题: ‘APP本地 Android 原生工程,并且原生工程里面有 Android 原生模块,如何发版?是用 Android Studio 直接发版还是用 HBuilderX 发版?’
答案:
需补充hbuilderx版本信息及具体发版需求细节。
是的,即使是原生工程,必须将Android原生模块封装成uni-app原生插件才能通过HBuilderX正常发版上线。直接使用Android Studio打包的APK不符合uni-app发布规范,无法正确集成原生模块。
关键流程:
- 开发原生模块:在Android Studio中完成开发调试,参考Android平台uni原生插件开发指南。
- 插件封装:按uni原生插件包格式要求打包为ZIP(根目录为插件ID,包含
android目录和package.json),放入项目nativeplugins目录。 - 正式发版:必须通过HBuilderX操作:
- 选择"发行" → "云打包",上传插件并配置参数。
- 不可勾选"自定义基座"(仅用于调试),需重新提交云端打包生成正式版APK。
- 直接使用Android Studio打包的APK无法通过应用商店审核。
原因说明:
- uni-app的正式版依赖云端打包机制统一处理插件兼容性、签名及64位支持(如Android商店审核要求)。
- 自定义基座仅用于本地调试,不能作为正式版发布(见云端插件打包发布指南)。
务必遵循此流程,否则原生模块无法生效且无法上架应用商店。
收起阅读 »对接uni-push 2.0官方未说明的三大坑,大家注意避免
1、第一个坑指定用户推送
指定推送push_clientid时项目必须集成uni统计且在manifest.json中配置push_clientid上传
"uniStatistics" : {
"enable" : true,
"collectItems" : {
"uniPushClientID" : true
}
}
配置后重新运行重启app后在云服务器的设备表(opendb-device)中要能查询到push_clientid才能实现指定用户推送,否则都是全量用户推送
2、iOS在线接收会闪退
uniPush.sendMessage({
"push_clientid": pushClientId, //填写上一步在uni-app客户端获取到的客户端推送标识push_clientid
"force_notification": true, //填写true,客户端就会对在线消息自动创建“通知栏消息”。
"title": "消息通知",
"content": content,
"payload": null,
"badge": 0,
"category": {
"harmony": "SUBSCRIPTION",
"huawei": "SUBSCRIPTION",
"vivo": "SUBSCRIPTION"
}
})
如上如果某个字段值出现null就会出现iOS在线推送APP就会闪退,这里我们一般会形成接口提供给自己的后端调用,所以所有字段都需要加非空判断
3、鸿蒙必须在App.vue文件中onLaunch里注册uni.onPushMessage才能接收通知栏推送
如果未注册的话在uni-push后台推送能收到,但是使用uniPush.sendMessage推送完全收不到,Android、iOS没有该问题
以上三个坑我踩了三天,希望对大家能有所帮助
1、第一个坑指定用户推送
指定推送push_clientid时项目必须集成uni统计且在manifest.json中配置push_clientid上传
"uniStatistics" : {
"enable" : true,
"collectItems" : {
"uniPushClientID" : true
}
}
配置后重新运行重启app后在云服务器的设备表(opendb-device)中要能查询到push_clientid才能实现指定用户推送,否则都是全量用户推送
2、iOS在线接收会闪退
uniPush.sendMessage({
"push_clientid": pushClientId, //填写上一步在uni-app客户端获取到的客户端推送标识push_clientid
"force_notification": true, //填写true,客户端就会对在线消息自动创建“通知栏消息”。
"title": "消息通知",
"content": content,
"payload": null,
"badge": 0,
"category": {
"harmony": "SUBSCRIPTION",
"huawei": "SUBSCRIPTION",
"vivo": "SUBSCRIPTION"
}
})
如上如果某个字段值出现null就会出现iOS在线推送APP就会闪退,这里我们一般会形成接口提供给自己的后端调用,所以所有字段都需要加非空判断
3、鸿蒙必须在App.vue文件中onLaunch里注册uni.onPushMessage才能接收通知栏推送
如果未注册的话在uni-push后台推送能收到,但是使用uniPush.sendMessage推送完全收不到,Android、iOS没有该问题
以上三个坑我踩了三天,希望对大家能有所帮助
收起阅读 »安卓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)
}
})
});
JavaScript 函数式编程完全指南
JavaScript 函数式编程完全指南
📚 目录
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 纯函数的定义
纯函数满足两个条件:
- 确定性:相同的输入永远返回相同的输出
- 无副作用:不修改外部状态,不依赖外部可变状态
// ✅ 纯函数示例
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 什么是高阶函数
高阶函数是至少满足以下条件之一的函数:
- 接受函数作为参数
- 返回一个函数
// 接受函数作为参数
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. 基础阶段
├── 理解纯函数概念
├── 掌握 map、filter、reduce
└── 理解不可变性
2. 进阶阶段
├── 学习高阶函数模式
├── 掌握函数组合和管道
└── 理解柯里化和偏应用
3. 实践阶段
├── 在项目中应用 FP 原则
├── 使用 FP 库(Ramda、Lodash/fp)
└── 构建自己的工具函数库
推荐资源
- 《JavaScript函数式编程指南》
- Ramda.js - 实用的函数式编程库
- Professor Frisby's Mostly Adequate Guide to FP
记住:函数式编程不是全有或全无的选择。你可以在现有代码中逐步引入函数式概念,慢慢体会它带来的好处!
JavaScript 函数式编程完全指南
📚 目录
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 纯函数的定义
纯函数满足两个条件:
- 确定性:相同的输入永远返回相同的输出
- 无副作用:不修改外部状态,不依赖外部可变状态
// ✅ 纯函数示例
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 什么是高阶函数
高阶函数是至少满足以下条件之一的函数:
- 接受函数作为参数
- 返回一个函数
// 接受函数作为参数
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. 基础阶段
├── 理解纯函数概念
├── 掌握 map、filter、reduce
└── 理解不可变性
2. 进阶阶段
├── 学习高阶函数模式
├── 掌握函数组合和管道
└── 理解柯里化和偏应用
3. 实践阶段
├── 在项目中应用 FP 原则
├── 使用 FP 库(Ramda、Lodash/fp)
└── 构建自己的工具函数库
推荐资源
- 《JavaScript函数式编程指南》
- Ramda.js - 实用的函数式编程库
- Professor Frisby's Mostly Adequate Guide to FP
记住:函数式编程不是全有或全无的选择。你可以在现有代码中逐步引入函数式概念,慢慢体会它带来的好处!
收起阅读 »基于UNIAPP的知识付费系统、题库考试APP定制案例分享,欢迎合作
有独立的题库APP也有综合的知识付费系统,产品技术稳定成熟,有需要的小伙伴加:yddapps
小程序APP UNIAPP开发
后端:php+mysql
有独立的题库APP也有综合的知识付费系统,产品技术稳定成熟,有需要的小伙伴加:yddapps
小程序APP UNIAPP开发
后端:php+mysql




