函数式编程

143 阅读5分钟

函数式编程是一种编程范式,主要是利用函数把运算过程封装起来,通过组合各种函数来计算结果。

函数式编程有两个基本特点:

  • 通过函数来对数据进行转换
  • 链式编程 (通过串联多个函数来求结果)

常见特性:

  • 声明式的代码
  • 不可变变量**:** 任何修改都会生成一个新的变量
  • 无副作用 **(函数只会用到传递给它的变量以及自己内部创建的变量,不会使用到其他变量,相同的输入一定会得到相同的输出
    **
  • 函数是一等公民:指的是函数与其他数据类型一样,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
  • 使用纯函数的代码绝不会更改或破坏全局状态,有助于提高代码的可测试性和可维护性
  • 函数式编程采用声明式的风格,易于推理,提高代码的可读性。

常见的函数式编程模型:

1.闭包

如果一个函数内部引用了该函数作用域外的其他局部变量,那么该函数就是一个闭包。

闭包的用途: 可以定义一些作用域局限的持久化变量,这些变量可以用来做缓存或者计算的中间量等

持久化局部变量

2.高阶函数 

指的是以函数作为参数传递,或以函数作为返回值。 **像:mapfilterreduce等等这些也属于高阶函数,**使用高阶函数会让我们的代码更清晰简洁

const arr = [5, 7, 1, 8, 4];
const sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue,0);
console.log(sum)//25

**debounce 和 throttle 函数也是高阶函数(闭包的使用)
防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的情况下只会调用一次,而节流的情况会每隔一定时间(参数wait)调用函数
**

防抖 (t时间段内多次触发事件只执行一次)每次执行的时候判断timer是否有值,有值则clearTimeout清空定时器,并且重新开启定时器,直到指定时间间隔内没有触发事件时才会真正执行事件的回调
最常用于button,input

function debounce(fn, delay) {
  let timer = null;

  return function () {
    const args = arguments;
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);    }, delay);
  }
}

 立即执行防抖函数:

不希望非要等到事件停止触发 n 秒后才执行;
希望触发后立刻执行函数,n 秒内连续触发不执行,然后等到停止触发 n 秒后,才可以重新触发执行。

function debounce(func, wait, immediate) {
  let timer;

  return function () {
    const context = this;
    const args = arguments;
    // 每次执行开始先清除上一次的定时器
    clearTimeout(timer); 
    if (immediate) {
      // 如果 timer 有值,表明 wait 秒内连续触发,所以不执行
      // wait 秒后 timer 的值置为 null, 再次触发,可立即执行
      var callNow = !timer;
      timer = setTimeout(function () {
        timer = null;
      }, wait);
      if (callNow) func.apply(context, args);
    } else {
      timer = setTimeout(function () {
        func.apply(context, args);
      }, wait);
    }
  };
}

节流隔一定的时间触发一次)触发函数事件后,指定间隔内多次触发不会执行,超过指定的时间间隔,才能进行下一次的函数调用
最常用于resize,scroll事件中处理

// 使用时间戳
function throttle(func, wait) {
  let preTime = 0;

  return function () {
    let nowTime = +new Date();
    let context = this;
    let args = arguments;

    if (nowTime - preTime > wait) {
      func.apply(context, args);
      preTime = nowTime;
    }
  };
}

// 定时器实现
function throttle(func, wait) {
  let timeout;

  return function () {
    let context = this;
    let args = arguments;

    if (!timeout) {
      timeout = setTimeout(function () {
        timeout = null;
        func.apply(context, args);
      }, wait);
    }
  };
}

call(),apply(),bind()的使用和实现

const obj = {
  a: 1,
  func: function () {
    console.log(this.a, arguments.length);
    return this.a + arguments.length
  }
}
const obj2 = {
  a: 2
}

/**********  call 开始  ***********/
function myCall (context, ...res) {
  if (typeof this !== 'function') {
    throw new TypeError('not funciton')
  }
  context = context || window
  
  context.fn = this;
  let result = context.fn(...res);
  delete context.fn;
  return result;
}
/************* call 结束 ************/
Function.prototype.myCall = myCall
obj.func.myCall(obj2,1,2);


/********** apply 开始 ***********/
function myApply(context, params) {
  if(typeof this != 'function') {
    throw new TypeError('not function')
  }
  context = context || window;
  
  context.fn = this;
  return context.fn(...params);
}
/*********** apply 结束 ************/
Function.prototype.myApply = myApply;
obj.func.myApply(obj2,[1,2,3]);


/**********  bind 开始  ***********/
function myBind(context, ...res) {
  const fn = this;
  return function (...args) {
    return fn.apply(context,[...res, ...args])  }
}
/************ bind 结束 ***********/
Function.prototype.myBind = myBind
const func2 = obj.func.myBind(obj2, 1)
func2(1,2)

3.函数柯里化(currying)

又称部分求值,柯里化函数会接收一些参数,然后不会立即求值,而是继续返回一个新函数,将传入的参数通过闭包的形式保存,等到被真正求值的时候,再一次性把所有传入的参数进行求值。

柯里化的作用:参数复用;延迟计算

参数复用
const _ = require("lodash");
const buildUriCurry = _.curry(buildUri);

const myGithubPath = buildUriCurry("https", "github.com");

const profilePath = myGithubPath("semlinker/semlinker");
const awesomeTsPath = myGithubPath("semlinker/awesome-typescript");

// 延迟计算
var add = function(x) { 
     return function(y) {
        return x + y;  
    };
};
var increment = add(1);
increment(2); // 3


const curry = function (fn) {
  let params = []
  const func = (...args) => {
    params = params.concat([...args])
    if(params.length >= fn.length) {
      return fn(...params)
    } else {
      return func
    }
  }
  return func
}

const add = function (a,b,c) {
  return [...arguments].reduce((sum, num) => sum += num, 0)
}
// add(1,2,3)

const curryAdd = curry(add)
curryAdd(1)(2)(3)

==》 codepen.io/alianzhang/…

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) { // 通过函数的length属性,来获取函数的形参个数
      return func.apply(this, args);
    } else {
      return function (...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  }
}

4.函数组合(Composition)

随着链式编程的数量增多,代码的可读性就会不断下降,函数组合可以用来解决这个问题;

为了实现函数的复用,我们通常会尽量保证函数的职责单一,通过对函数进行组合,来实现特定的功能。