五分钟带你解析JS函数编程

介绍

JavaScript中的函数式编程是一种编程范式,它将函数视为一等公民,并强调使用纯函数、不可变数据和函数组合来构建程序。函数式编程在JavaScript中得到了广泛应用,特别是在处理异步操作、函数组合和数据转换等方面。

React 和 Vue 3 都在向函数式编程靠拢,尤其是在最新的版本中,它们都引入了一些函数式编程的概念和特性。

React 在 Hooks 的引入中,采用了函数组件作为主要的组件形式,这使得组件可以更加纯粹地关注状态和渲染。Hooks 提供了一系列的函数,如 useStateuseEffectuseMemo 等,使得状态管理和副作用处理更加简洁和可组合。这些函数可以帮助开发者遵循函数式编程的原则,例如使用纯函数来处理状态更新,避免共享状态和可变数据等。

Vue 3 在 Composition API 的引入中,也采用了函数式的风格。Composition API 允许开发者将逻辑按照功能进行组合,而不是按照生命周期钩子进行组织。这样可以更好地重用逻辑和提高代码的可读性。Composition API 还提供了一些函数,如 reactivecomputedwatch 等,用于处理响应式数据和副作用。这些函数使得状态管理和副作用处理更加灵活和可控。

并且函数作为JavaScript的一等公民,这意味着函数可以像其他数据类型一样进行操作和传递。

//1.函数可以被赋值给变量
const greet = function (name) {
  console.log(`Hello, ${name}!`)
}
greet('Alice') // 输出 "Hello, Alice!"
//2函数可以作为参数传递给其他函数: 
function sayHello(greetingFunction) {
    greetingFunction("Alice");
  }
  
  function greet(name) {
    console.log(`Hello, ${name}!`);
  }
  
sayHello(greet); // 输出 "Hello, Alice!
//3 函数可以作为其他函数的返回值
function createMultiplier(multiplier) {
    return function(number) {
      return number * multiplier;
    };
  }
  
  const double = createMultiplier(2);
  console.log(double(5)); // 输出 10
//4.函数可以存储在对象中:
const person = {
    name: "Alice",
    greet: function() {
      console.log(`Hello, ${this.name}!`);
    }
  };
  
person.greet(); // 输出 "Hello, Alice!"
//这种灵活性使得函数可以更加灵活地使用和组合,
//从而实现更好的代码可读性、可维护性和可重用性。

接下来让我们学习一下函数式编程吧

JavaScript 中函数式编程的主要特点

1.纯函数

纯函数是指在相同的输入下,返回相同的输出,并且没有副作用的函数。纯函数不依赖于外部状态,使得代码更加可靠、可测试和可维护。

//纯函数应该满足以下条件:
//相同的输入总是产生相同的输出。
//函数执行过程中不会改变外部状态。
//函数没有产生可观察的副作用
function add(a, b) {
  return a + b
}  //是纯函数

function test(a,b){
console.log("a")
} //不是纯函数 有可观察的副作用

function HelloWorld(props) {
    props.info = {}
    props.info.name = 'why'
} //不是纯函数 直接修改了`props`对象的属性

//修改为纯函数
 function HelloWorld(props) {
    const newProps = { ...props };
    newProps.info = {};
    newProps.info.name = 'ZhaiMou';
    return newProps;
 }
  1. 不可变性:函数式编程中的数据结构是强调不可变的,即一旦创建就不能再被修改。这样做的好处是避免了共享状态并发冲突,因为不需要对数据进行加锁,也可以提高程序的可靠性。
const arr = [1, 2, 3];
// 使用concat()方法创建一个新的数组,而不是修改原始数组
const newArr = arr.concat(4);
const newArr1 = [...arr, 4]
console.log(arr);     // 输出: [1, 2, 3]
console.log(newArr);  // 输出: [1, 2, 3, 4]
console.log(newArr1);  // 输出: [1, 2, 3, 4]
const obj = { name: "Alice", age: 25 };
// 使用Object.assign()方法创建一个新的对象,而不是修改原始对象
const newObj = Object.assign({}, obj, { age: 30 });
console.log(obj);     // 输出: { name: "Alice", age: 25 }
console.log(newObj);  // 输出: { name: "Alice", age: 30 }
  1. 高阶函数:函数式编程中的函数可以作为参数传递给其他函数,也可以作为返回值返回给调用者。这样做的好处是可以提高代码的复用性和灵活性
// 高阶函数示例
function multiplyBy(factor) {
    return function(number) {
      return number * factor;
    }
  }
  
const double = multiplyBy(2); // 创建一个乘以2的函数
console.log(double(5)); // 输出: 10

函数柯里化 ⭐⭐⭐⭐⭐

// 原始函数
function add(a, b, c) {
  return a + b + c;
}
//柯里化函数
function curryAdd(a) {
    return function(b) {
      return function(c) {
        return a + b + c;
      };
    };
}
// 柯里化函数单一职责的原则
function sum(x) {
  x = x + 2

  return function(y) {
    y = y * 2

    return function(z) {
      z = z * z

      return x + y + z
    }
  }
}
console.log(sum(10)(20)(30))
// 柯里化函数的实现hyCurrying
function hyCurrying(fn) {
  function curried(...args) {
    // 判断当前已经接收的参数的个数, 可以参数本身需要接受的参数是否已经一致了
    // 1.当已经传入的参数 大于等于 需要的参数时, 就执行函数
    // 函数的length为参数的个数 fn.length
    if (args.length >= fn.length) {
      // fn(...args)
      // fn.call(this, ...args)
      return fn.apply(this, args)
    } else {
      // 没有达到个数时, 需要返回一个新的函数, 继续来接收的参数
      function curried2(...args2) {
        // 接收到参数后, 需要递归调用curried来检查函数的个数是否达到
        return curried.apply(this, args.concat(args2))
      }
      return curried2
    }
  }
  return curried
}

let curryAdd1 = hyCurrying(add) 
console.log(curryAdd1(10, 20, 30)) //60
console.log(curryAdd1(10, 20)(30)) //60
console.log(curryAdd1(10)(20)(30)) //60
  1. 函数组合:函数组合是指将多个函数组合成一个新函数。这种特点可以让代码更简洁、高效和可读性更强。
// 函数组合示例
function addOne(number) {
    return number + 1;
  }
  
  function multiplyByTwo(number) {
    return number * 2;
  }
  
  function compose(func1, func2) {
    return function(arg) {
      return func1(func2(arg));
    };
  }
  
  const addOneAndMultiplyByTwo = compose(multiplyByTwo, addOne); // 组合函数
  console.log(addOneAndMultiplyByTwo(3)); // 输出: 8
通用组合函数实现
function hyCompose(...fns) {
  let length = fns.length
  for (let i = 0; i < length; i++) {
    if (typeof fns[i] !== 'function') {
      throw new TypeError('Expected arguments are functions')
    }
  }

  function compose(...args) {
    let index = 0
    let result = length ? fns[index].apply(this, args) : args
    while (++index < length) {
      result = fns[index].call(this, result)
    }
    return result
  }
  return compose
}
function double(m) {
  return m * 2
}

function square(n) {
  return n * 2
}

var newFn = hyCompose(double, square)
console.log(newFn(10)) //40
  1. 函数式编程支持延迟计算,也就是推迟表达式的求值直到需要的时候。这种方式可以减少不必要的计算,并提高性能。例如,JavaScript中的lazy evaluation特性允许我们使用生成器、迭代器和惰性操作来实现延迟计算。
// 延迟计算示例
function* generateNumbers() { // 定义生成器函数
    let number = 0;
    while (true) {
      yield number++;
    }
  }

  const numbers = generateNumbers(); // 创建一个生成器对象
  console.log(numbers.next().value); // 输出: 0
  console.log(numbers.next().value); // 输出: 1

由于生成器函数是惰性求值的,即只有在需要时才会计算值,因此它可以减少不必要的计算,并提高性能。常见的还有异步请求和惰性加载和防抖节流等等

  1. 数据转换和操作
// 数据转换和操作示例
const numbers = [1, 2, 3, 4];

const doubledNumbers = numbers.map(num => num * 2); // 映射操作
console.log(doubledNumbers); // 输出: [2, 4, 6, 8]

const evenNumbers = numbers.filter(num => num % 2 === 0); // 过滤操作
console.log(evenNumbers); // 输出: [2, 4]

const sum = numbers.reduce((acc, curr) => acc + curr, 0); // 归约操作
console.log(sum); // 输出: 10

我们使用数组的mapfilterreduce方法对数字数组进行了转换和操作。我们可以使用这些操作以声明性的方式处理数据,而不需要显式的循环和状态管理。

文章更多作为自我学习,有错欢迎指出。

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值