函数式编程

在开始这次的记录之前要先罗列下面四个重点:

  • 为什么要学习函数式编程以及什么是函数式编程
  • 函数式编程的特性(纯函数,柯里化,函数组合等等)
  • 函数式编程的应用场景
  • 函数式编程Lodash(有很多,挑一个来讲)

学习函数式编程的理由

  • 函数式编程随着react的流行变得越来越受到关注
  • Vue 3开始使用函数式编程
  • 函数式编程可以抛弃烦人的this
  • 打包过程中可以更好的利用tree shaking过滤掉没有用的代码
  • 方便测试,方便并行处理
  • 上面列举到了Lodash,其实还有:underscore,ramda等等

什么是函数式编程

Functional Programming FP,FP就是编程范式之一,我们常常听说的编程范式还有面向对象,面向过程
这里不需要赘述面向对象编程的思维方式,简单理解就是:把现实世界里的事物抽象成程序世界里的类和对象,通过封装,继承和多态来演示事件事物的联系。
函数式编程的思维方式就是:把现实世界的事物与事物之间的联系抽象到程序里(对运算过程的抽象)
举个例子:

  • 程序的本质:根据输入通过某种运算得到相对应的输出,程序开发过程中会涉及到很多有输入和输出的函数
  • x -> f(联系,映射)->y,y=f(x)
  • 函数式编程的函数指的不是程序里的函数(function),指的是数学中的函数映射关系:y=sin(x),x和y这样的关系
  • 相同的输入始终必须要得到相同的输出
  • 函数式编程用来描述数据(函数)之间的映射关系
// 非函数式
let number1 = 1
let number2 = 2
let sumRes = number1 + number2
console.log(sumRes)

// 函数式
function add (num1, num2) {
    return num1 + num2
}
let sumRes = add(2, 3)
console.log(sumRes)

前置知识

有三个要素接下来要单独讲解
1.函数是一等公民
2.高阶函数
3.闭包

函数是一等公民

MDN上的一等公民科普

  • 函数可以存储在变量之中
  • 函数作为参数
  • 函数作为返回值

在JavaScript中函数就是一个普通的对象(可以通过 new Function()),我们可以把函数存储到变量/数组中,还可以作为另一个函数的参数和返回值,甚至可以在程序运行的时候通过new Function('alert(1)')来构造一个新的函数

// 把函数赋值给变量
let fn = function() {
    console.log('Hello, First-class function')
}
fn()

// 一个优化例子
const BocchiBlogController = {
    index (posts) { return Views.index(posts) },
    show (post) { return Views.show(post) },
    create (attrs) { return Db.create(attrs) },
    update (post, attrs) { return Db.update(post, attrs) },
    destory (post) { return Db.destory(post) }
}
// 优化之后
const BocchiBlogController = {
    index: Views.index,
    show: Views.show,
    create: Db.create,
    update: Db.update,
    destory: Db.destory
}

函数一等公民是我们学习高阶函数,柯里化的关键基础

高阶函数

什么是高阶函数?

  • 高阶函数(Higher-order function)

    • 可以把函数作为参数传递给另一个函数
    • 可以把函数作为另一个函数的返回结果
  • 函数作为参数
// 高阶函数-函数作为参数
function forEach(array, fn) {
  for (let i = 0; i < array.length; i++) {
    fn(array[i]);
  }
}

let arr = [1, 2, 3, 4];

forEach(arr, function (item) {
  console.log(item);
});

// filter
function filter(array, fn) {
  let resArr = [];
  for (let i = 0; i < array.length; i++) {
    if (fn(array[i])) {
      resArr.push(array[i]);
    }
  }
  return resArr;
}

// test
let arrTwo = [1, 2, 3, 4];
console.log(
  filter(arrTwo, function (item) {
    return item % 2 === 0;
  })
);
  • 将函数作为返回值
// 高阶函数-将函数作为返回值
function makeFn() {
  let msg = "test message";
  return function () {
    console.log(msg);
  };
}

const fn = makeFn();
fn();

/**
也可以这样写:这样的意思就是
调用makeFn()返回的函数
*/
makeFn()()
/**
 * once:给dom元素注册一个事件,只会注册一次
 * lodash中也有一个once函数,对其中的内容只会执行一次
 * 使用场景:支付场景
 */
function once(fn) {
  let done = false;
  return function () {
    if (!done) {
      done = true;
      return fn.apply(this, arguments);
    }
  };
}

let pay = once(function (money, people) {
  console.log(`pay ${money} ${people}`);
});

pay(5, "1");
pay(5, "1");
pay(5, "1");

上述代码实现了一个高阶函数 once,它的作用是确保被包装的函数 fn 只会执行一次。下面是该函数的运行逻辑:

  1. 定义一个变量 done,用来表示是否已经执行过函数 fn,初始值为 false。
  2. 返回一个匿名函数,这个函数内部通过判断 done 的值,来决定是否执行 fn 函数。
  3. 如果 done 为 false,那么说明 fn 还没有被执行过,将 done 置为 true,并调用 fn.apply(this, arguments) 来执行 fn 函数,并返回其结果。
  4. 如果 done 为 true,那么说明 fn 已经被执行过了,直接返回 undefined。

在实际应用场景中,有些函数只需要被执行一次就足够了,例如事件监听器、定时器等等,如果不加限制,这些函数可能会被重复执行,导致程序出现异常或者性能下降。使用 once 函数可以有效地避免这种情况发生,从而提高程序的健壮性和效率。

fn.apply(this, arguments) 是 JavaScript 中的一个内置函数,它可以用来在指定的上下文中调用一个函数,并且以数组形式传递参数。具体来说,它有以下两个作用:

  1. 改变函数执行时的 this 指向。
    在 JavaScript 中,this 默认指向全局对象(即 window 对象),但是在某些情况下我们需要改变 this 的指向,例如绑定事件、调用构造函数等。使用 apply 函数可以将指定的上下文对象赋值给 this,从而改变函数执行时的 this 指向。
  2. 以数组形式传递参数。
    在 JavaScript 中,函数的参数数量和类型并不固定,使用 apply 函数可以解决这个问题。它允许我们将参数数组作为第二个参数传递给被调用的函数,这样函数就可以根据实际情况接收任意数量和类型的参数。

例如,在 fn.apply(this, arguments) 中,this 指的是当前函数执行时的上下文对象,而 arguments 则是一个伪数组对象,包含了函数执行时所传递的所有参数。apply 函数会将 arguments 转换成数组形式,并作为 fn 函数的参数传递进去,从而实现在指定上下文中调用函数,并且以数组形式传递参数的功能。

需要注意的是,apply 函数与 call 函数类似,唯一的区别就是参数的传递方式不同。call 函数是将每个参数依次传递给被调用的函数,而 apply 函数是将所有参数打包成数组再传递。

以上是AI对于这个once的理解(很到位

  • 高阶函数的意义:

    • 抽象可以帮我们屏蔽细节,只需要关注与我们的目标
    • 高阶函数是用来抽象通用的问题

常用的高阶函数

  • forEach
  • map
  • filter
  • every
  • some
  • find/findIndex
  • reduce
  • sort

模拟常用的高阶函数(map,every,some)

// 模拟map,some,every

// map
const map = (arr, fn) => {
  let newArr = [];
  for (let val of arr) {
    newArr.push(fn(val));
  }
  return newArr;
};

// test map
let mapArr = [1, 2, 3, 4];
console.log(map(mapArr, (item) => item * item));

// every
const every = (arr, fn) => {
  let result = true;
  for (let val of arr) {
    result = fn(val);
    if (!result) break;
  }
  return result;
};

// test every
let everyArr1 = [1, 2, 4];
let everyArr2 = [2, 4, 6];
console.log(every(everyArr1, (item) => item % 2 === 0));
console.log(every(everyArr2, (item) => item % 2 === 0));

// some
const some = (arr, fn) => {
  let result = false;
  for (let val of arr) {
    result = fn(val);
    if (result) break;
  }
  return result;
};

// test some
let someArray1 = [1, 2, 4];
let someArray2 = [2, 4, 6];
console.log(some(someArray1, (item) => item % 2 === 1));
console.log(some(someArray2, (item) => item % 2 === 1));

闭包

闭包 (Closure):函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包

  • 可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域成员

闭包的本质是函数和其所能访问的外部变量的组合体。在 JavaScript 中,每当我们创建一个函数时,它都会形成一个作用域(scope),该作用域中包含了函数内部的变量和参数,并且可以访问外部环境中的变量和参数。当函数执行完毕后,该作用域会被销毁,其中的变量和参数也被释放。但是,如果函数内部定义了其他函数,并且这些函数引用了外部函数中的变量或参数,则这些内部函数就形成了一个闭包。闭包使得这些内部函数能够继续访问并操作其外部函数中的变量或参数,即使外部函数已经执行完毕。因此,通过使用闭包,我们可以实现一些非常有用的编程技巧,如数据封装、模块化开发等。通俗地说,闭包就像是一个“超级变量”,它包含了函数和其所能访问的外部变量,使得我们可以更加灵活地使用和控制变量的作用范围。

闭包案例

function power(powNum) {
  return function (baseNumber) {
    return Math.pow(baseNumber, powNum);
  };
}

let power2 = power(2);
let power3 = power(3);

console.log(power2(3));
console.log(power2(4));
console.log(power3(5));

这段代码中的闭包发生在函数 power 内部。

当调用 power(2) 时,会创建并返回一个内部函数,该内部函数使用了外部函数 power 中的参数 powNum,并将其值设置为 2。由于 power 函数已经执行完毕并返回了内部函数,因此该内部函数形成了一个闭包,并且可以访问并使用 powNum=2 这个变量。

类似地,当调用 power(3) 时,也会创建并返回一个新的内部函数,该函数使用外部函数 power 中的参数 powNum,并将其值设置为 3。同样地,该内部函数也形成了一个闭包,并且可以访问并使用 powNum=3 这个变量。

最后,我们将这两个内部函数分别赋值给变量 power2power3,并通过它们来计算不同底数的平方或立方值。

总之,这段代码中的闭包是在函数 power 内部形成的,它们保留了外部函数 power 的参数 powNum 的值,并允许内部函数继续访问和使用这个值,即使外部函数已经执行完毕。

最后修改:2023 年 03 月 28 日
收款不要了,给孩子补充点点赞数吧