高阶函数、命令式编程和声明式编程

150 阅读3分钟

这是我参与⌈第五届青训营⌋笔记创作活动的第2天

高阶函数、命令式编程和声明式编程

什么是高阶函数?

  • 以函数作为参数
  • 以函数作为返回值
  • 常用于函数装饰器
let HOFO = function (fn) {
  return function (...args) {
    return fn.apply(this, args);
  };
};

高阶函数的好处

  • 高阶函数的魅力在于它的可重复利用性,如果不是高阶函数,mapfilterreduce 等强大的数组函数就不可能存在。
  • 使 JavaScript 适合函数式编程的原因是它接受高阶函数。

常用高阶函数示例

防抖和节流

节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效

  • once

    • 让函数只执行一次,常用于节流

    • let once = function (fn) {
        return function (...args) {
          if (fn) {
            setTimeout(() => {
              fn = null;
            });
            return fn.apply(this, args);
          }
        };
      };
      
  • throttle

    • 让函数在执行后的一段时间不能被再次触发

    • let throttle = function (fn, time = 500) {
        let timer;
        return function (...args) {
          if (timer == null) {
            fn.apply(this, args);
            timer = setTimeout(() => {
              timer = null;
            }, time);
          }
        };
      };
      
  • consumer

    • 将多次触发的任务加入任务队列,依次间隔执行

    • function consumer(fn, time){
        let tasks = [],
            timer;
        
        return function(...args){
          tasks.push(fn.bind(this, ...args));
          if(timer == null){
            timer = setInterval(() => {
              tasks.shift().call(this)
              if(tasks.length <= 0){
                clearInterval(timer);
                timer = null;
              }
            }, time)
          }
        }
      }
      

    防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

    • debounce

      • let debounce = function (fn, time) {
          let timer;
          return function (...args) {
            clearTimeout(timer);
            timer = setTimeout(() => {
              return fn.apply(this, args);
            }, time);
          };
        };
        

非纯函数和纯函数(pure function)

什么是纯函数和非纯函数

  • 纯函数是指在给定输入的参数下,输出总是确定的,不依赖于外界环境

    • 例如:

      let add = (a, b) => a + b;
      //add返回的值只与输入参数有关
      
  • 非纯函数依赖上下文

    • 例如:

      let count = 0;
      let add = () => count++;
      //add返回的值依赖于count的值
      

如果代码中非纯函数过多,代码会变得难以维护,所以我们要减少写出非纯函数

将非纯函数转化为纯函数

const isIterable = obj => obj != null 
  && typeof obj[Symbol.iterator] === 'function';

function iterative(fn) {
  return function(subject, ...rest) {
    if(isIterable(subject)) {
      const ret = [];
      for(let obj of subject) {
        ret.push(fn.apply(this, [obj, ...rest]));
      }
      return ret;
    }
    return fn.apply(this, [subject, ...rest]);
  }
}

这里的iterative和isIterable都是纯函数,它们能方便地对可迭代对象批量执行函数

例如假如现在有一个setColor函数能改变元素的颜色,如果这时我们要对元素批量执行,再定义一个setColors的话就产生了两个非纯函数,而如果我们利用上面的iterative函数就可以直接将setColor函数传入其中,实现批量操作,减少了非纯函数的产生。

命令式和声明式编程

以改变一个按钮的状态为例

  • 命令式编程关注怎么做

    • switcher.onclick = function(evt){
        if(evt.target.className === 'on'){
          evt.target.className = 'off';
        }else{
          evt.target.className = 'on';
        }
      }
      
  • 声明式编程关注做什么

    • function toggle(...actions){
        return function(...args){
          let action = actions.shift();
          actions.push(action);
          return action.apply(this, args);
        }
      }
      
      switcher.onclick = toggle(
        evt => evt.target.className = 'off',
        evt => evt.target.className = 'on'
      );
      

看起来区别不大但当这个按钮有三种状态时,在命令式编程下我们需要重写if-else的逻辑,而声明式编程下我们只需要在toggle里再多传入evt => evt.target.className = 'warn'就可以实现。

声明式语言在某些场景下可以极大地简化代码。通过归纳抽离部分,实现对通用代码的简化操作。而且,声明式编程通常是以数据(或者数据流)为导向的,声明式编程通常可以更好的用于处理数据。