「index」的搜索结果

  • 【Javascript修炼篇】JS中的函数式编程

    介绍:

    函数式编程(FP)是一种编程范式,这意味着一种基于一些原则来思考软件构建的方法,比如 纯函数、不可变性、一等与高阶函数、函数组合、闭包、声明式编程、递归、引用透明性、柯里化 和 部分应用

    当这些原则有效地应用到 JavaScript 中时,可以使得代码更加模块化、可维护、健壮、易于理解、可测试,并且能够优雅地处理复杂的问题。

    这篇文章看起来可能有点长,但不会那么理论化。

    让我们开始逐一实验吧:

    1. 纯函数:

    两条规则:

    1. 给定相同的输入,总是返回相同的结果。
    2. 不产生副作用。

    用处: 容易重构,使代码更具灵活性和适应性。

    例子 1:

    JavaScript
    // 不纯的函数。
    let a = 4;
    const multiplyNumbers = (b) => a *= b;
    
    multiplyNumbers(3);
    console.log(a); // 第一次调用:12
    > 12
    multiplyNumbers(3);
    console.log(a); // 第二次调用:36
    > 36
    
    // 修改了外部变量,所以不是纯函数。
    JavaScript
    // 纯函数版本。
    const multiplyNumbers = (x,y) => x * y;
    
    multiplyNumbers(2, 3);
    > 6

    例子 2:

    JavaScript
    // 不纯的函数。
    addNumberarr = (arr, num) => {
    arr.push(num);
    };
    const testArr = [1,2,3];
    addNumberarr(testArr, 4);
    
    console.log(testArr);
    > [1, 2, 3, 4]
    
    // 修改了输入数组,所以不是纯函数。
    JavaScript
    // 上面的纯函数版本。
    addNumberarr = (arr, num) => {
    return [...arr, num];
    };
    const testArr = [1,2,3];
    addNumberarr(testArr, 4);
    > [1, 2, 3, 4]

    JS 内置的纯函数:

    JavaScript
    arr.reduce()
    arr.map()
    arr.filter()
    arr.concat()
    arr.slice()
    arr.each()
    arr.every()
    ... - 扩展语法

    JS 内置的非纯函数:

    JavaScript
    arr.splice()
    arr.push()
    arr.sort()
    Math.random()

    2. 不可变性:

    一旦创建就不能改变状态的对象。

    一个简单的例子就是使用 slice 方法来帮助你轻松理解它的含义。

    JavaScript
    const arr = [1,2,3,4];
    
    const slicedArray = arr.slice(1,2);
    
    slicedArray
    > [2]
    
    arr
    > [1, 2, 3, 4]

    如果你看上面的例子,slice 并没有改变原始数组 arr。而下面的例子则不同:

    JavaScript
    const arr = [1,2,3,4];
    
    arr.push(5);
    > 5
    
    arr
    > [1, 2, 3, 4, 5]

    原始数组 arr 被修改了。这并不是说我们不应该使用 push,但是在大多数情况下我们可以避免这种情况。一个简单的例子是:

    JavaScript
    const arr = [1,2,3,4];
    
    const newArr = [...arr, 5];
    
    arr
    > [1, 2, 3, 4]
    
    newArr
    > [1, 2, 3, 4, 5]

    上面的所有都是简单例子,可能不会造成任何问题。但是,如果我们在整个文件中尽可能多地修改同一个对象,就会带来许多问题。因为我们需要跟踪这个对象被修改了多少次以及以何种方式被修改。

    为了解决这个问题,我们需要避免修改对象。

    3. 一等函数

    一等函数是指把函数当作一等公民的概念,意味着它们被视为常规变量或值。这让函数可以像字符串或数字等其他数据类型一样被操作和使用。这允许函数作为参数传递给其他函数,从其他函数返回值,以及被赋值给变量。JavaScript 支持这一点。

    它打开了强大的编程技术的大门,比如高阶函数、函数组合,以及抽象的创建。

    4. 高阶函数:

    一个函数可以接受另一个函数作为参数或者返回一个函数作为结果,这样的函数被称为高阶函数。

    1. 返回一个函数的函数
    JavaScript
    const higherOrderFunc = function() {
        return function() {
            return 12;
        }
    }
    
    // 返回下面的函数,所以它是高阶函数。
    higherOrderFunc(); 
    > ƒ () {
            return 12;
        }
    
    higherOrderFunc()();
    > 12
    1. 接受一个函数作为参数的函数
    JavaScript
    const testFunc = function(x) {
        return x + 12;
    }
    
    // 接受函数作为参数。
    const higherOrderFunc = function(testFunc) {
        return testFunc(8);
    }
    
    higherOrderFunc(testFunc);
    > 20

    例子 1:

    JavaScript
    function calculate(operation, numbers) {
        return operation(numbers);
    }
    
    function addition(numbers) {
        let sum = 0;
        for (const number of numbers) {
            sum+=number;
        }
        return sum;
    }
    
    function multiply(numbers) {
        let sum = 1;
        for (const number of numbers) {
            sum*=number;
        }
        return sum;
    }
    
    const numbers = [1,2,3,4,5];
    console.log(calculate(addition, numbers));
    > 15
    
    console.log(calculate(multiply, numbers));
    > 120
    
    // calculate(multiply, numbers) - 传递函数作为参数时不加括号。

    高阶函数的好处:

    减少代码重复
    单一职责

    在 JavaScript 中,函数可以接受原始类型或对象作为参数并返回相同类型,称为一阶函数。

    JS 内置的高阶函数有:

    arr.reduce(), arr.forEach(), arr.filter(), arr.map()

    5. 函数组合:

    这是一种方法,其中将一个函数的结果传给下一个函数。

    JavaScript
    const add = (x, y) => x+y;
    
    const subtract = (x) => x-4;
    
    const multiply = (x) => x * 8;
    
    // add 的结果传给 subtract,其结果再传给 multiply。
    const result = multiply(subtract(add(2, 3)));
    
    result;
    > 8

    看起来很清晰,但如果我们要一个接一个地调用更多函数会怎么样呢?让我们试试更干净的方法。

    JavaScript
    const compose = (...functions) => x => functions.reduceRight((total, f) => f(total), x);
    
    const add = x => x+2;
    
    const subtract = x => x-1;
    
    const multiply = x => x * 8;
    
    compose(multiply, subtract, add)(2);
    > 24

    我们也可以使用 reduce 来实现:

    JavaScript
    const pipe = (...functions) => x => functions.reduce((total, f) => f(total), x);
    
    const add = x => x+2;
    
    const subtract = x => x-1;
    
    const multiply = x => x * 8;
    
    pipe(add, subtract, multiply)(2);
    > 24

    pipe – 从左到右执行。
    compose – 从右到左执行。

    6. 声明式编程:

    声明式: 告诉 做什么

    命令式: 告诉 怎么做

    例子: 找出部门为 ‘justCode’ 的员工及其工资总和。

    命令式风格:

    JavaScript
    const employees = [
    {id: 1, name: 'james', dept: 'admin', salary: 10000},
    {id: 1, name: 'Tom', dept: 'finance', salary: 10000},
    {id: 1, name: 'peter', dept: 'justCode', salary: 12500},
    {id: 1, name: 'tunner', dept: 'justCode', salary: 14500},
    ];
    
    const justCodeDept = [];
    
    // 根据部门名称筛选员工。
    for (let i=0; i<employees.length; i++) {
      if (employees[i].dept === 'justCode') {
        justCodeDept.push(employees[i]);
      }
    }
    
    // 计算 justCodeDept 员工的工资总和。
    let summation = 0;
    for (j = 0; j<justCodeDept.length; j++) {
      summation = summation + justCodeDept[j].salary;
    }
    
    console.log(summation);

    声明式风格:

    JavaScript
    const employees = [
    {id: 1, name: 'james', dept: 'admin', salary: 10000},
    {id: 1, name: 'Tom', dept: 'finance', salary: 10000},
    {id: 1, name: 'peter', dept: 'justCode', salary: 12500},
    {id: 1, name: 'tunner', dept: 'justCode', salary: 14500},
    ];
    
    console.log(employees.filter(item => item.dept === 'justCode').reduce(((previousValue, currentValue) => previousValue += currentValue.salary), 0));

    7. 柯里化:

    将接收多个参数的函数拆分成一系列函数,每个函数只接收单个参数。

    例子 1:

    通常我们写:

    JavaScript
    function addition(x, y, z) {
        return x + y + z;
    }
    
    addition(1, 2, 3);
    > 6

    柯里化版本:

    JavaScript
    function addition(x) {
        return function addY(y) {
            return function addZ(z) {
                return x + y + z;
            }
        }
    }
    
    addition(1)(2)(3);
    > 6

    使用箭头函数:

    JavaScript
    addition = (x) => (y) => (z) => x + y + z;
    
    addition(1)(2)(3);
    > 6

    例子 2:

    JavaScript
    function formWelcomNote(name) {
        name = `Hello ${name}, `;
        return function(location) {
            location = `Welcome to ${location},`;
            return function(section) {
                return `${name}${location} Please visit ${section} section`
            }
        }
    }
    
    formWelcomNote('Yester')('VK Just Code Articles')('JS Articles');
    > 'Hello Yester, Welcome to VK Just Code Articles, Please visit JS Articles section'

    我们也可以这样写:

    JavaScript
    formWelcomNote = (name) => {
        name = `Hello ${name}, `;
        return (location) => {
            location = `Welcome to ${location},`;
            return (section) => {
                return `${name}${location} Please visit ${section} section`
            }
        }
    }
    
    formWelcomNote('Yester')('VK Just Code Articles')('JS Articles');
    > 'Hello Yester, Welcome to VK Just Code Articles, Please visit JS Articles section'

    例子 3:

    JavaScript
    function calculation(fn) {
        switch (fn) {
            case 'add': return (a, b) => a + b;
            case 'sub': return (a, b) => a - b;
            case 'mul': return (a, b) => a * b;
            case 'div': return (a, b) => a / b;
        }
    }
    console.log(calculation('mul')(4, 2));

    8. 部分应用:

    你为一个函数固定一定数量的参数,并生成一个新的带有较少参数的函数。这个新函数可以在稍后的时间用剩下的参数来调用。部分应用有助于创建更加专业和可重用的函数。

    例子:

    JavaScript
    function add(a, b) {
      return a + b;
    }
    
    // 部分应用第一个参数
    const add2 = add.bind(null, 2);
    
    console.log(add2(5));  // 输出:7 (2 + 5)
    console.log(add2(8));  // 输出:10 (2 + 8)

    9. 引用透明性:

    JavaScript 中的一个表达式可以用它的值来替代,这种特性叫做引用透明性。

    JavaScript
    const add = (x, y) => x + y;
    
    const multiply = (x) => x * 4;
    
    // add (3, 4) 可以被替换为 7 —— 引用透明性。
    
    multiply(add(3, 4)); 
    > 28
    
    multiply(add(3, 4));
    > 28
    JavaScript
    const arr = [];
    const add = (x, y) => {
        const addition = x + y;
        arr.push(addition);
        return addition;
    }
    
    const multiply = (x) => x * 4;
    
    // 在这里,我们不能用 7 替换 add(3, 4),因为它会影响程序逻辑。
    multiply(add(3, 4));
    > 28
    
    multiply(add(3, 4));
    > 28

    10. 闭包:

    闭包让你可以从内部函数访问外部函数的作用域。

    JavaScript
    function outer() {
        const name = 'test';
        function inner() {
            // 'name' 从外部函数可以访问到内部函数中
            console.log(name);
        }
        inner();
    }
    outer();
    
    > test
    JavaScript
    function outerAdd(x) {
        return function(y) {
            return x + y;
        };
    }
    
    const outer12 = outerAdd(12); // x 为 12.
    const outer14 = outerAdd(14); // x 为 14.
    
    const outer12Result = outer12(12); // y 为 12.
    console.log(outer12Result);
    > 24
    
    const outer14Result = outer14(14); // y 为 14.
    console.log(outer14Result);
    > 28

    或者,你也可以像下面这样使用箭头函数:

    JavaScript
    outerAdd = x => y => x + y;
    
    const outer12 = outerAdd(12);
    const outer14 = outerAdd(14);
    
    const outer12Result = outer12(12);
    console.log(outer12Result);
    > 24
    
    const outer14Result = outer14(14);
    console.log(outer14Result);
    > 28

    使用闭包的计数器示例:

    JavaScript
    function outer() {
        let counter = 0;
        return function inner() {
            counter += 1;
            return counter;
        }
    }
    const out = outer();
    
    console.log(out());
    console.log(out());
    console.log(out());
    
    > 1
    > 2
    > 3

    11. 递归:

    递归是一种编程技巧,在其中函数通过自我调用来解决问题。

    例子:

    JavaScript
    function factorial(n) {
      if (n === 0 || n === 1) {
        return 1;
      } else {
        return n * factorial(n - 1);
      }
    }
    
    console.log(factorial(5));  // 输出:120 (5 * 4 * 3 * 2 * 1)
    console.log(factorial(0));  // 输出:1 (按定义)

    在这个例子中,factorial 函数计算给定数 n 的阶乘。它使用了 n === 0 和 n === 1 的基本情况,阶乘定义为 1。对于其它任何值的 n,函数递归地调用自身,并将结果乘以 n。

    当你调用 factorial(5) 时,递归调用序列如下所示:

    JavaScript
    factorial(5)
      -> 5 * factorial(4)
           -> 4 * factorial(3)
                -> 3 * factorial(2)
                     -> 2 * factorial(1)
                          -> 1
                     <- 2 * 1 = 2
                <- 3 * 2 = 6
           <- 4 * 6 = 24
      <- 5 * 24 = 120

    如果有任何概念上的例子需要补充,请随时评论。

    希望通过今天这篇文章,让你对JS中的函数式编程有了更好的理解。并且可以在日常的开发过程中进行灵活应用,以提高开发效率。