(六)前端javascript中的函数式编程技巧

为什么要学习函数式编程

  • 函数式编程是种编程范式,它主要关注的是运算过程本身,而忽略过程发生的环境。
  • 函数式编程是声明式编程范式,它关注的是运算结果,而忽略运算过程。

函数式编程的特点

  • 函数式编程(Functional Programming),FP 是编程范式之一,我们常听说的还有
    面相过程,面相对象编程

  • 把现实世界的事物和事物之间的联系抽象到程序世界,对运算过程进行抽象

  • 相同的输入,得到相同的输出(纯函数)

  • 函数式编程是用来描述数据函数之间的关系

//非函数式

var a = 1;
var b = 2;
var c = a + b;
console.log(c);

// 函数式编程
var add = function (a, b) {
  return a + b;
};
console.log(add(1, 2));
// 函数式编程
var add = (a, b) => a + b;
console.log(add(1, 2));

函数是第一公民

  • 函数可以存储在变量中
//把函数赋值给变量
var add = function (a, b) {
  return a + b;
};
add();

-函数可以作为参数传递给另一个函数

  • 函数可以作为返回值返回给另一个函数;

高阶函数

  • HOC(Higher-order function)
  • 可以把函数作为另一个函数的返回值
//高阶函数
function myForEach(array, fn) {
  for (var i = 0; i < array.length; i++) {
    fn(array[i], i, array);
  }
}
var arr = [1, 2, 3];
var fn = function () {
  // console.log(arguments);
  console.log(arguments[0]);
};

myForEach(arr, fn);
  • 封装自定义的 filter
//封装自定义filter
function filter(array, fn) {
  let newArr = [];
  for (let i = 0; i < array.length; i++) {
    if (fn(array[i], i, array)) {
      newArr.push(array[i]);
    }
  }
  return newArr;
}
const res = filter(arr, (item) => item % 2 == 0);
console.log("🚀 ~ res:", res);
  • 作为函数的返回值
//作为返回值返回
function makeFn() {
  let str = "this is a string";
  return function () {
    console.log(str);
  };
}

const testFn = makeFn();
testFn();
  • 只触发一次的函数封装
//只触发一次
function once(cb) {
  let isFlag = false;
  return function () {
    if (isFlag) return;

    console.log("helloCitizen");
    cb && cb.apply(this, arguments);
    isFlag = true;
  };
}

const testOnce = once(function () {
  const args = arguments;
  console.log("hello", ...arguments);
});
testOnce("a", "b"); // 这里会执行
testOnce("c", "d"); //这里就不会执行
testOnce("e", "f"); //这里也不会执行
/**
 * helloCitizen
   hello a b
 */

使用高阶函数的意义

  • 函数式编程的精髓就是高阶函数,它可以让代码更加简洁,可读性更高。
  • 屏蔽实现细节,只需关注我们的业务逻辑。目标
// 函数式编程
let arr1 = [11, 22, 32];
forEach(arr1, (item) => {
  console.log(item);
});

function forEach(array, fn) {
  for (let index = 0; index < array.length; index++) {
    fn && fn(array[index], index, array);
  }
}

常用的高阶函数

  • map,every,some,filter
//模拟实现map
function map(array, fn) {
  for (let index = 0; index < array.length; index++) {
    array[index] = fn(array[index], index, array);
  }
  return array;
}

let array2 = [1, 2, 3];

const res2 = map(array2, (item) => item * 10);
console.log("🚀 ~ res2:", res2); //🚀 ~ res2: [ 10, 20, 30 ]

//模拟实现every

function every(array, fn) {
  for (let index = 0; index < array.length; index++) {
    if (!fn(array[index], index, array)) {
      return false;
    }
  }
  return true;
}
let array3 = [1, 2, 3];
const res3 = every(array3, (item) => item % 2 == 0);
console.log("🚀 ~ res3:", res3);
const res4 = every(array3, (item) => item > 0);
console.log("🚀 ~ res3:", res4);

//🚀 ~ res3: false
// 🚀 ~ res3: true

//模拟实现some
function some(array, fn) {
  for (let index = 0; index < array.length; index++) {
    if (fn(array[index], index, array)) {
      return true;
    }
  }
  return false;
}
let array5 = [1, 2, 3];
const res5 = some(array5, (item) => item % 2 == 0);
console.log("🚀 ~ res5:", res5);
//🚀 ~ res5: true

闭包

  • 闭包就是能够读取其他函数内部变量的函数。
  • 闭包就是函数 A 返回了一个函数 B,并且函数 B 中使用了函数 A 中的变量。
  • 这些变量也叫自由变量
function maxPower(power) {
  return function (num) {
    return Math.pow(num, power);
  };
}

const fn1 = maxPower(2);
const res6 = fn1(3);
console.log("🚀 ~ res6:", res6);

纯函数

  • 相同的输入始终会得到相同的输出

  • lodash 库的函数都是纯函数

  • 纯函数的好处就是可以缓存结果,避免重复计算

  • slice 是纯函数,但是 concat,splice 不是

  • 相同的输入,永远得到相同的输出,那我们就可以缓存结果,避免重复计算

lodash 库的几个函数测试

const array = [1, 2, 3];

const first = _.head(array);
console.log("🚀 ~ first:", first); // first: 1
const last = _.last(array);
console.log("🚀 ~ last:", last); // first: 3

_([1, 2]).forEach(function (value) {
  console.log(value);
});

const res1 = _.groupBy([1, 2, 3, 4, 5], (value) => value % 2 == 0);
console.log("🚀 ~ res1:", res1);

const res2 = _.includes([1, 2, 3], 2);
console.log("🚀 ~ res2:", res2);

const res3 = _.includes({ user: "barney", age: 36, active: true }, "barney");
console.log("🚀 ~ res3:", res3);

const res4 = _.invokeMap(
  [
    [3, 2, 4, 5],
    [5, 2, 7, 3, 1],
  ],
  "sort"
);
console.log("🚀 ~ res4:", res4);
  • 模拟实现纯函数的缓存作用
function getArea(r) {
  console.log(r);
  return Math.PI * r * r;
}

var object = { a: 1, b: 2 };
var other = { c: 3, d: 4 };

//范式1
const res1 = _.memoize(getArea);
const values = res1(6);
console.log("🚀 ~ values:", values);
//范式2
const res2 = _.memoize(_.values);
const values2 = res2(object);
console.log("🚀 ~ values2:", values2);
object.a = 2;
const values3 = res2(object);
console.log("🚀 ~ values3:", values3);

//实现纯函数的缓存
const res1 = _.memoize(getArea);
const values = res1(6);
const values2 = res1(6);
const values3 = res1(6);
console.log("🚀 ~ values:", values);
console.log("🚀 ~ values:", values);
console.log("🚀 ~ values:", values);

/**
 * 6
demo3.js:26 🚀 ~ values: 113.09733552923255
demo3.js:27 🚀 ~ values: 113.09733552923255
demo3.js:28 🚀 ~ values: 113.09733552923255

输入三次6,输出三次113.09733552923255,但是只调用了getArea函数一次,说明纯函数缓存起作用了
 */

function myMemoize(fn) {
  let cache = {};
  return function () {
    const arg = JSON.stringify(arguments);
    if (cache[arg]) {
      return cache[arg];
    }
    const res = fn.apply(this, arguments);
    cache[arg] = res;
    return res;
  };
}

//优化myMemoize
function myMemoize1(fn) {
  let cache = {};
  return function () {
    const arg = JSON.stringify(arguments);
    cache[arg] = cache[arg] || fn.apply(this, arguments);
    return cache[arg];
  };
}

const test = myMemoize(getArea);
const res1 = test(6);
const res2 = test(6);
const res3 = test(6);
console.log("🚀 ~ res1:", res1);
console.log("🚀 ~ res2:", res2);
console.log("🚀 ~ res3:", res3);

const test1 = myMemoize1(getArea);
const opp1 = test1(6);
const opp2 = test1(6);
const opp3 = test1(6);
console.log("🚀 ~ opp1:", opp1);
console.log("🚀 ~ opp2:", opp2);
console.log("🚀 ~ opp3:", opp3);

/**
 * 6
demo3.js:66 🚀 ~ res1: 113.09733552923255
demo3.js:67 🚀 ~ res2: 113.09733552923255
demo3.js:68 🚀 ~ res3: 113.09733552923255
demo3.js:2 6
demo3.js:74 🚀 ~ opp1: 113.09733552923255
demo3.js:75 🚀 ~ opp2: 113.09733552923255
demo3.js:76 🚀 ~ opp3: 113.09733552923255
 */

副作用

  • 函数式编程中,副作用指的是函数执行过程中,除了返回函数值之外,还对外部产生了可见的影响。
  • 如果一个函数依赖外部的状态,那么这个函数就是有副作用的,无法保证相同的输出
//不纯的函数
let min = 18;
function checkAge(age) {
  return age >= min;
}
  • 副作用的来源
    • 函数内部访问了全局变量
    • 函数内部修改了全局变量
    • 函数内部调用了其他函数
    • 函数内部调用了 I/O 操作
    • 函数内部调用了 Date.now()
    • 函数内部调用了 Math.random()
    • 函数内部调用了 setTimeout()
    • 函数内部调用了 setInterval()
    • 函数内部调用了 setImmediate()
    • 函数内部调用了 process.nextTick()

柯里化

  • 柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果
  • 柯里化是把一个多参数的函数转换成多个单参数的函数,且返回值是一个函数。
  • 柯里化是函数式编程的一个重要的概念,它把一个多参数的函数转换成多个单参数的函数。
//硬编码,不提倡,反例
function checkAge(age) {
  let min = 18;
  return age > min;
}

//普通的纯函数
function checkAge(min, age) {
  return age > min;
}

//柯里化
function checkAge2(min) {
  return function (age) {
    return age > min;
  };
}
//es6语法
const checkAge2 = (min) => (age) => age > min;

const checkAge18 = checkAge2(18);
console.log(checkAge18(20)); //true
const checkAge20 = checkAge2(20);
console.log(checkAge20(18)); //false
  • 模拟实现柯里化的原理
//lodash中的纯函数
const abc = function (a, b, c) {
  return a + b + c;
};
const curried = _.curry(abc);

console.log(curried(1)(2)(3)); //6
console.log(curried(1, 2)(3)); //6
console.log(curried(1)(2, 3)); //6
console.log(curried(1, 2, 3)); //6

//abc形参的个数
console.log(abc.length); //3

//模拟实现柯里化
function myCurry(fn) {
  return function myCurried(...rest) {
    if (rest.length < fn.length) {
      return function () {
        return myCurried(...[...rest, ...arguments]);
      };
    }
    return fn.apply(this, rest);
  };
}
const res2 = myCurry(abc);
console.log("mycurry", res2(1)(2, 3)); //6
console.log("mycurry", res2(1, 2)(3)); //6
console.log("mycurry", res2(1)(2, 3)); //6
console.log("mycurry", res2(1, 2, 3)); //6
console.log("mycurry", res2(1)(2)(3)); //6

函数组合

  • 函数组合指的是把多个函数组合成一个新的函数,新函数的计算结果,是各个函数运算后的结果结合在一起。
  • 函数组合的目的是把多个函数运算的结果,合并成一个结果。
function compose(f, g) {
  return function (value) {
    return f(g(value));
  };
}

const add = (x) => x + 1;
const multiply = (x) => x * 2;

const getData = compose(add, multiply);
const res = getData(1);
console.log(res);
  • 模拟封装组合函数
//函数组合
function compose(f, g) {
  return function (value) {
    return f(g(value));
  };
}

const add = (x) => x + 1;
const multiply = (x) => x * 2;
const add2 = (x) => x + 100;
const join = (x) => x + x;

const getData = compose(add, multiply);
const res = getData(1);
console.log(res);

//模拟函数组合
function myCompose() {
  var args = Array.prototype.slice.call(arguments);
  return function () {
    var result = args[0].apply(this, arguments);
    for (let i = 1; i < args.length; i++) {
      result = args[i](result);
    }
    return result;
  };
}

//基础版本
function myCompose1(...args) {
  return function (...payload) {
    var result = payload[0];
    for (let i = args.length - 1; i >= 0; i--) {
      result = args[i](result);
    }
    return result;
  };
}

//改进版
function myCompose2(...args) {
  return function (value) {
    return args.reverse().reduce((prev, fn) => fn(prev), value);
  };
}
//升级版
const myCompose3 =
  (...args) =>
  (value) =>
    args.reverse().reduce((prev, fn) => fn(prev), value);

const getData2 = myCompose1(add, multiply, add2);
const res2 = getData2(1);
console.log("res2", res2); // 203

const getData3 = myCompose2(add, multiply, add2, join);
const res3 = getData3(1);
console.log("res3", res3); // 203

const getData4 = myCompose3(add, multiply, add2, join);
const res4 = getData4(1);
console.log("res4", res4); // 205
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值