HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

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

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

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

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

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

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

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

插件市场 提现

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

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

底部面板可独立窗口

折叠面板

希望可以独立

希望可以独立

JavaScript 函数式编程完全指南

偏应用 柯里化 函数式编程

JavaScript 函数式编程完全指南

📚 目录

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

1. 什么是函数式编程

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

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

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

2. 纯函数

2.1 纯函数的定义

纯函数满足两个条件:

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

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

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

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

2.2 非纯函数示例

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

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

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

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

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

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

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

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

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

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

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

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

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

2.4 纯函数的好处

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

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

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

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

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

3. 不可变性

3.1 什么是不可变性

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

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

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

3.2 数组的不可变操作

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

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

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

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

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

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

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

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

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

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

3.3 对象的不可变操作

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4. 高阶函数

4.1 什么是高阶函数

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

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

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

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

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

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

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

4.2 常用高阶函数模式

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4.3 创建专用函数

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

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

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

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

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

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

5. 核心数组方法

5.1 map - 转换每个元素

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

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

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

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

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

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

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

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

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

5.2 filter - 过滤元素

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

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

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

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

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

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

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

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

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

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

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

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

5.3 reduce - 归约为单一值

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return accumulator;  
}  

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

5.4 其他有用的数组方法

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

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

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

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

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

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

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

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

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

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

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

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

5.5 方法链式调用

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

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

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

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

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

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

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

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

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

6. 函数组合与管道

6.1 函数组合 (Compose)

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

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

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

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

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

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

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

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

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

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

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

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

6.2 管道 (Pipe)

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

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

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

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

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

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

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

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

6.3 异步管道

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

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

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

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

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

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

6.4 条件组合

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

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

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

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

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

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

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

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

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

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

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

7. 柯里化与偏应用

7.1 柯里化 (Currying)

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

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

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

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

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

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

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

const curriedMultiply = curry(multiply);  

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

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

7.2 柯里化的实际应用

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

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

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

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

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

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

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

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

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

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

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

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

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

7.3 偏应用 (Partial Application)

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

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

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

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

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

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

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

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

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

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

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

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

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

7.4 柯里化 vs 偏应用

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

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

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

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

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

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

8. 实战案例

8.1 数据处理管道

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

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

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

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

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

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

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

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

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

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

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

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

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

8.2 表单验证系统

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return { isValid, fields: merged };  
};  

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

const validateUserForm = validateForm(userFormSchema);  

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

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

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

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

8.3 状态管理

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

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

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

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

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

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

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

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

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

        default:  
            return state;  
    }  
};  

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

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

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

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

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

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

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

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

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

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

// 取消订阅  
unsubscribe();

8.4 函数式工具库

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

console.log(avgAgeByDept);

📖 总结

函数式编程的核心原则

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

学习路径建议

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

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

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

推荐资源

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

继续阅读 »

JavaScript 函数式编程完全指南

📚 目录

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

1. 什么是函数式编程

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

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

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

2. 纯函数

2.1 纯函数的定义

纯函数满足两个条件:

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

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

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

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

2.2 非纯函数示例

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

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

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

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

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

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

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

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

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

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

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

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

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

2.4 纯函数的好处

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

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

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

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

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

3. 不可变性

3.1 什么是不可变性

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

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

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

3.2 数组的不可变操作

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

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

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

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

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

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

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

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

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

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

3.3 对象的不可变操作

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4. 高阶函数

4.1 什么是高阶函数

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

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

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

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

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

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

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

4.2 常用高阶函数模式

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4.3 创建专用函数

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

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

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

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

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

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

5. 核心数组方法

5.1 map - 转换每个元素

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

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

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

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

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

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

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

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

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

5.2 filter - 过滤元素

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

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

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

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

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

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

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

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

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

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

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

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

5.3 reduce - 归约为单一值

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return accumulator;  
}  

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

5.4 其他有用的数组方法

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

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

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

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

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

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

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

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

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

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

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

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

5.5 方法链式调用

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

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

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

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

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

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

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

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

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

6. 函数组合与管道

6.1 函数组合 (Compose)

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

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

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

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

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

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

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

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

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

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

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

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

6.2 管道 (Pipe)

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

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

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

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

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

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

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

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

6.3 异步管道

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

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

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

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

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

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

6.4 条件组合

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

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

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

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

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

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

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

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

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

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

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

7. 柯里化与偏应用

7.1 柯里化 (Currying)

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

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

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

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

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

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

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

const curriedMultiply = curry(multiply);  

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

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

7.2 柯里化的实际应用

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

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

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

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

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

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

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

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

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

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

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

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

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

7.3 偏应用 (Partial Application)

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

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

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

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

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

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

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

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

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

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

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

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

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

7.4 柯里化 vs 偏应用

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

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

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

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

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

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

8. 实战案例

8.1 数据处理管道

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

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

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

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

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

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

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

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

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

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

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

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

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

8.2 表单验证系统

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return { isValid, fields: merged };  
};  

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

const validateUserForm = validateForm(userFormSchema);  

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

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

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

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

8.3 状态管理

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

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

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

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

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

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

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

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

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

        default:  
            return state;  
    }  
};  

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

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

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

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

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

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

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

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

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

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

// 取消订阅  
unsubscribe();

8.4 函数式工具库

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

console.log(avgAgeByDept);

📖 总结

函数式编程的核心原则

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

学习路径建议

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

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

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

推荐资源

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

收起阅读 »

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

鸿蒙征文

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

获奖名单

一等奖

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

二等奖

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

三等奖

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

方向二特别奖

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

奖项设置

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

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

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

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

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


奖品领取

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

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


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

继续阅读 »

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

获奖名单

一等奖

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

二等奖

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

三等奖

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

方向二特别奖

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

奖项设置

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

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

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

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

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


奖品领取

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

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


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

收起阅读 »

个人开发者承接热门打螺丝,挪车,三消换皮抖音微信小游戏,全职低价外包接单

游戏 外包接单

低价换皮包上线

有需要开发的并能看得上我的请联系我哈

vx:tbszjx

低价换皮包上线

有需要开发的并能看得上我的请联系我哈

vx:tbszjx

个人开发者承接app、小程序、网页外包,全职外包接单

uniapp 外包接单

全职在家承接外包,多年外包经验,个人开发者,绝对实惠靠谱,有很多款线上应用(度是自己开发的,自己独立完成,可查)

可做商城类,社交类,工具类,任务平台类,mes 类,扫码收银/分账交易类 等,除了游戏和带颜色的,其他度可以开发

可承接安卓/IOS、各个端的小程序、H5网页、PC网页开发,从前端到后端,全度会,一条龙服务,

可免费提供上架服务

掌握技术
前端:uniapp 、uniappx、vue
后端:Golang 、php

有需要开发的并能看得上我的请联系我哈

vx:wu1020yt

继续阅读 »

全职在家承接外包,多年外包经验,个人开发者,绝对实惠靠谱,有很多款线上应用(度是自己开发的,自己独立完成,可查)

可做商城类,社交类,工具类,任务平台类,mes 类,扫码收银/分账交易类 等,除了游戏和带颜色的,其他度可以开发

可承接安卓/IOS、各个端的小程序、H5网页、PC网页开发,从前端到后端,全度会,一条龙服务,

可免费提供上架服务

掌握技术
前端:uniapp 、uniappx、vue
后端:Golang 、php

有需要开发的并能看得上我的请联系我哈

vx:wu1020yt

收起阅读 »

ios 无法使用 plus.runtime.launchApplication,打开小红书,还有别的办法吗

打开外部应用

plus.runtime.launchApplication({ action: "xhs://" });
plus.runtime.launchApplication({ action: "xiaohongshu://" });

这两个方法都无效

继续阅读 »

plus.runtime.launchApplication({ action: "xhs://" });
plus.runtime.launchApplication({ action: "xiaohongshu://" });

这两个方法都无效

收起阅读 »

小白的我分享补充UniappX安卓本地打包流程中的两个细节

本文只针对UniappX的安卓端本地打包(iOS端我还没看)

首先,说明一下,我不会android studio原生开发,一直都是用的云打包。最近接了个项目,必须本地打包,就专门研究了一下。
我现在已经把UniappX的本地打包走通了,对于下面两点,对于小白可能会蒙圈,我就蒙圈了。所以写下来分享一下。可能有不对的地方,请及时指正。
昨天新装的:Android Studio Otter | 2025.2.1 Patch 1

以下是UniappX的本地打包过程:
打包第一步:在android studio里创建一个Empty Activity的工程,包名使用HBuilderX里的项目包名。
打包第二步:照着官方文档一步步做,绝对好使:https://doc.dcloud.net.cn/uni-app-x/native/use/android.html

我在这里说两点容易掉坑里的地方:

1.按文档改完后,启动调试发现打开的还是项目原本的默认页面 hello android.
解决方法:打开/app/src/main/AndroidManifest.xml重新设置首页,下面的是我修改后的,不要复制粘贴,比对一下,缺哪个粘哪个。

<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools">  

    <application  
        android:name="io.dcloud.uniapp.UniApplication"  
        android:allowBackup="true"  
        android:dataExtractionRules="@xml/data_extraction_rules"  
        android:fullBackupContent="@xml/backup_rules"  
        android:icon="@mipmap/ic_launcher"  
        android:label="@string/app_name"  
        android:roundIcon="@mipmap/ic_launcher_round"  
        android:supportsRtl="true"  
        android:theme="@style/Theme.Mytest004">  
        <activity  
            android:name="io.dcloud.uniapp.UniAppActivity"  
            android:configChanges="orientation|keyboard|keyboardHidden|smallestScreenSize|screenLayout|screenSize|mcc|mnc|fontScale|navigation|uiMode"  
            android:exported="true"  
            android:label="@string/app_name"  
            android:screenOrientation="portrait"  
            android:theme="@style/UniAppX.Activity.DefaultTheme"  
            android:windowSoftInputMode="adjustResize"  
            tools:replace="android:label,android:exported,android:theme,android:configChanges,android:windowSoftInputMode,android:screenOrientation">  
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />  

                <category android:name="android.intent.category.LAUNCHER" />  
            </intent-filter>  
        </activity>  

        <meta-data  
            android:name="DCLOUD_UNI_APPID"  
            android:value="你的项目appid" />  

        <meta-data  
            android:name="DCLOUD_CHANNEL"  
            android:value="googleplay" />  
    </application>  
</manifest>

2.针对插件
1.“前端组件-通用组件”,这类组件不涉及原生代码,因此在生成本地资源包的时候,会被生成到src目录下,也就是和pages在同一个目录下:unpackage/resources/app-android/uniappx/app-android/src/
因此,在复制资源的时候,就把index.kt,pages,uni_modules一起粘贴过去就可以了。

2.“UTS插件-api插件”,这类插件因为有原生代码,所在生成本地资源包的时候,会被生成到unpackage/resources/app-android/目录下,在这里你会看到一个uni_modules文件夹,里面就是你的插件。如果里面没有你的插件,说明你的页面没有引用这个插件或者代码有错误。

UTS插件在安卓端kotlin代码顶端的正确包名应该是这种格式的:uts.sdk.modules.starViewtime
因此在Android Studio中创建模块时,包名必须与uni-app X插件的包名完全一致,包名也要叫uts.sdk.modules.starViewtime,因为如果两边包名不一样,插件就不起作用了。

接下来重点来了:目录结构必须严格按照包名的层次结构创建,java后面的文件夹需要手动创建。

  • 例如: 刚才创建的模块文件夹下的/src/main/java/uts/sdk/modules/starViewtime/

最后把插件文件复制进去,这样你的UTS原生插件就好使了。

继续阅读 »

本文只针对UniappX的安卓端本地打包(iOS端我还没看)

首先,说明一下,我不会android studio原生开发,一直都是用的云打包。最近接了个项目,必须本地打包,就专门研究了一下。
我现在已经把UniappX的本地打包走通了,对于下面两点,对于小白可能会蒙圈,我就蒙圈了。所以写下来分享一下。可能有不对的地方,请及时指正。
昨天新装的:Android Studio Otter | 2025.2.1 Patch 1

以下是UniappX的本地打包过程:
打包第一步:在android studio里创建一个Empty Activity的工程,包名使用HBuilderX里的项目包名。
打包第二步:照着官方文档一步步做,绝对好使:https://doc.dcloud.net.cn/uni-app-x/native/use/android.html

我在这里说两点容易掉坑里的地方:

1.按文档改完后,启动调试发现打开的还是项目原本的默认页面 hello android.
解决方法:打开/app/src/main/AndroidManifest.xml重新设置首页,下面的是我修改后的,不要复制粘贴,比对一下,缺哪个粘哪个。

<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools">  

    <application  
        android:name="io.dcloud.uniapp.UniApplication"  
        android:allowBackup="true"  
        android:dataExtractionRules="@xml/data_extraction_rules"  
        android:fullBackupContent="@xml/backup_rules"  
        android:icon="@mipmap/ic_launcher"  
        android:label="@string/app_name"  
        android:roundIcon="@mipmap/ic_launcher_round"  
        android:supportsRtl="true"  
        android:theme="@style/Theme.Mytest004">  
        <activity  
            android:name="io.dcloud.uniapp.UniAppActivity"  
            android:configChanges="orientation|keyboard|keyboardHidden|smallestScreenSize|screenLayout|screenSize|mcc|mnc|fontScale|navigation|uiMode"  
            android:exported="true"  
            android:label="@string/app_name"  
            android:screenOrientation="portrait"  
            android:theme="@style/UniAppX.Activity.DefaultTheme"  
            android:windowSoftInputMode="adjustResize"  
            tools:replace="android:label,android:exported,android:theme,android:configChanges,android:windowSoftInputMode,android:screenOrientation">  
            <intent-filter>  
                <action android:name="android.intent.action.MAIN" />  

                <category android:name="android.intent.category.LAUNCHER" />  
            </intent-filter>  
        </activity>  

        <meta-data  
            android:name="DCLOUD_UNI_APPID"  
            android:value="你的项目appid" />  

        <meta-data  
            android:name="DCLOUD_CHANNEL"  
            android:value="googleplay" />  
    </application>  
</manifest>

2.针对插件
1.“前端组件-通用组件”,这类组件不涉及原生代码,因此在生成本地资源包的时候,会被生成到src目录下,也就是和pages在同一个目录下:unpackage/resources/app-android/uniappx/app-android/src/
因此,在复制资源的时候,就把index.kt,pages,uni_modules一起粘贴过去就可以了。

2.“UTS插件-api插件”,这类插件因为有原生代码,所在生成本地资源包的时候,会被生成到unpackage/resources/app-android/目录下,在这里你会看到一个uni_modules文件夹,里面就是你的插件。如果里面没有你的插件,说明你的页面没有引用这个插件或者代码有错误。

UTS插件在安卓端kotlin代码顶端的正确包名应该是这种格式的:uts.sdk.modules.starViewtime
因此在Android Studio中创建模块时,包名必须与uni-app X插件的包名完全一致,包名也要叫uts.sdk.modules.starViewtime,因为如果两边包名不一样,插件就不起作用了。

接下来重点来了:目录结构必须严格按照包名的层次结构创建,java后面的文件夹需要手动创建。

  • 例如: 刚才创建的模块文件夹下的/src/main/java/uts/sdk/modules/starViewtime/

最后把插件文件复制进去,这样你的UTS原生插件就好使了。

收起阅读 »

我想问有人安卓离线打包成功的没? 我怎么都跑不起来,官方下载的sdk中的案例都跑不起来!

Android App离线打包

如有成功的 能给个能跑的空包不~~~~ 解决一个错误又来一个 烦死了都!!!

如有成功的 能给个能跑的空包不~~~~ 解决一个错误又来一个 烦死了都!!!

基于UNIAPP的知识付费系统、题库考试APP定制案例分享,欢迎合作

有独立的题库APP也有综合的知识付费系统,产品技术稳定成熟,有需要的小伙伴加:yddapps
小程序APP UNIAPP开发
后端:php+mysql

有独立的题库APP也有综合的知识付费系统,产品技术稳定成熟,有需要的小伙伴加:yddapps
小程序APP UNIAPP开发
后端:php+mysql

快手去水印

快手

我喜欢在快手上看一些生活小技巧的短视频,想把它们保存到手机里随时看。

这个工具操作很简单,复制链接就能下载无水印版,方便我整理和归类。

https://iris.findtruman.io/web/ks-qsy?share=L

继续阅读 »

我喜欢在快手上看一些生活小技巧的短视频,想把它们保存到手机里随时看。

这个工具操作很简单,复制链接就能下载无水印版,方便我整理和归类。

https://iris.findtruman.io/web/ks-qsy?share=L

收起阅读 »