View on GitHub

Yinjie - GitHub.io

Welcome to the Yinjie's notes

高阶函数简单应用

上篇文章中我们介绍了什么是高阶函数。此章中,以例子中的实际场景,体验一下高阶函数能带给我们什么。 继续由浅入深

场景

假设有一个页面,现在需求针以不同群体用户做一次页面主题的针对性改造,包括其中的样式、文案等等。

这个的确不是什么困难的事,当然方法也有千千万。至少手动找到变化的地方逐个条件变更总是可以的。

用高阶函数式解决

从利于以后维护、可扩展性出发,上面提到的方式显然不行,代码和可读性和易用性就差了一截,对原代码的改动也很大,容易出错。

高阶函数可更好的解决此类问题,它可以对一个函数的执行(环境)加入上下文,而上面提到的“手动变更”肯定也是一系列的代码执行来实现的,我们只需要变更这些数据源就可以了,不用在具体的语句上做大的修改。

假设此页面执行变量的代码有以下:

document.body.classLise.add('xiaohong');                // xiaogang、xiaozhang、xiaoming......
document.getElementById('eleID').innerText = '小红';     // 小刚、小张、小明......

我们用上篇所提到的两种形式,可以做以下的改变:

作为回调

将以上业务代码包入一个回调中执行,通过执行回调参数的“父级”方法,来传递主题数据。

在这之前我们先定义一个简单的主题数据格式,相应的判断当前主题的一个方法:

var ThemeData = {
  theme1: {
    clazz: 'xiaohong',
    name: '小红'
  },
  theme1: {
    clazz: 'xiaohong',
    name: '小红'
  },
  ...
};

/**
 * 获取当前主题
 * @param {obj} themes ThemeData
 * @return {obj} {class, name}
 */
function getCurrentTheme(themes) {
  ...
  return theme;
}

准备好后,我们开始改造

function initTheme(businessCallback) {
  var themeData = getCurrentTheme(ThemeData);
  businessCallback(themeData);
}
//原有代码的改造
initTheme(function (themeData) {
  document.body.classLise.add(themeData.clazz);
  document.getElementById('eleID').innerText = themeData.name;
});

如此,我们对原有代码只是做了一些必要而精减的改动,主题的数据和判断都不会涉及到。这也应该是大家第一反应能想到的方法。

作为返回方法

再来换一下形式:

function initTheme1() {
  var themeData = getCurrentTheme(ThemeData);
  return function (businessCallback) {
    businessCallback(themeData);
  }
}
//原有代码的改造
initTheme1()(function (themeData) {
  document.body.classLise.add(themeData.clazz);
  document.getElementById('eleID').innerText = themeData.name;
});

这个是不是有种‘脱了裤子放屁’的感脚…,实际还没有第一种方式简读。的确是,那我们再“稍”变一下:

function initTheme2() {
  var themeData = getCurrentTheme(ThemeData);
  var _this = {};
  _this.themeData = themeData;
  return function (businessCallback) {
    businessCallback.call(_this);
  }
}
//原有代码的改造
initTheme2()(function () {
  document.body.classLise.add(this.clazz);
  document.getElementById('eleID').innerText = this.name;
});

作为回调的执行方法中不需要用一个参数传数据了,而是直接修改了 this,变得更优雅亲切一点。 这也是主要说明的一点,高阶函数可以更深入的修改回调函数的属性执行环境

我们再把上面几种例子总结的“整齐”一点:

// for example 1:
function Bussiness(themeData) {
  document.body.classLise.add(themeData.clazz);
  document.getElementById('eleID').innerText = themeData.name;
}

initTheme1()(Bussiness)

上面只是将例子1中的业务代码抽出去了,不过看着是不是有点眼熟。。。 下面对例子2做一次复杂点的改变,你可能会更眼熟一些:

假设我们的业务代码是一个 class,通过其中的 render 方法进行业务渲染:

// for example 2:
class Bussiness {
  constructor() {
    this.bussinessDomain = 'userTheme';
  }
  render() {
    // 原有的其它业务代码
    // ...
  }
}

function initTheme2() {
  var themeData = getCurrentTheme(ThemeData);
  function makeClass(A) {
    function _A() {
      this.themeData = themeData;
      this.render = function () {
        A.prototype.render.call(this);
        document.body.classLise.add(this.themeData.clazz);
        document.getElementById('eleID').innerText = this.themeData.name;
      }
    }
    _A.prototype = A.prototype;
    _A.prototype.constructor = A;
    return function () {
      const _a = new _A();
      A.prototype.constructor.apply(_a, arguments);
      return _a;
    };
  }
  return makeClass;
}

export default initTheme2()(Bussiness);

// console.log(new Bussiness());
// console.log(new (initTheme2()(Bussiness))());

因篇幅有限,以上代码只用来演示,实际中坑很多,慎用。

上面的代码是通过高阶函数对一个构造类进行内容的改变,“透明”的传入一些参数和环境变量。这也是 react-redux 的基本原理之一,可能在原 react 代码几乎不变的情况下套用 react-redux,通过它来控制 this.statethis.props,达到最终的效果。 此种思想在进行代码重构、框架兼容性升级时非常值得借鉴。