笔记7—函数式编程

47 阅读3分钟

一、函数式编程的出现

发展历程:

  1. 命令(脚本)式编程 → 以一行代码为单元,逐行运行
  2. 面向对象编程 → 以对象为操作单位
  3. 函数式编程 → 以行数为操作单位

从面试题开始

  1. url中是如何展示数组的

    location.search => '?name[]=progressive$%coding&name[]=objective$%coding'
    
  2. 参数提取,拼接数组

    ['progressive$%coding', 'objective$%coding']
    
  3. 转换成数组对象存储

    [{name: 'Progressive Coding'}, {name: 'Objective Coding'}]
    

解:

  1. 字符串拆分数组
  2. 字符串 → key value 遍历
const _array = ['progressive$%coding', 'objective$%coding'];
const _objArr = [];

const nameParser = (array, objArr) => {
	array.forEach(item => {
		// 将参数拆分成两部分
		let names = item.split('$%');
		let newName = [];
		
		// 遍历处理两部分的参数
		names.forEach(name => {
			let nameItem = name[0].toUpperCase() + name[1].splice(1);

			newName.push(nameItem);
		})
		objArr.push({
			name: newName.join(' ');
		})
	})
	return objArr;
}

问题:

  1. 过程存在逻辑包裹,需要看完整段代码才清楚代码在做什么,依赖注释
  2. 存在临时变量,首尾封闭,难以扩展

解决方案

  1. 需求分析: 数组 → 数组对象 ⇒ [字符串 → 对象]

    nameParser ⇒ [objHelper :: string → object]

  2. 功能明确:

    objHelper = formatName + assembleObj

  3. 拆分功能:

    objHelper = [(split + capitalize + join)] + assembleObj

  4. 代码实现

    const _array = ['progressive$%coding', 'objective$%coding'];
    
    // 原子操作
    
    const assembleObj = (key x)=>{
    	let obj = {};
    
    	obj[key] = x;
    	return obj;
    }
    
    const capitalize = str=>str[0].toUpperCase() + str.slice(1);
    
    // 声明结构
    
    // 组装合并(join(' '), map(capitalize), split('$%'))
    const formatName = 
    
    // 组装合并(assembleObj('name'), formatName()
    const objHelper = 
    
    const nameParser = map(objHelper);
    
    nameParser(_array);
    

面试题:如何正确使用遍历

  • for:通用
  • forEach:遍历逻辑处理(每一项都处理)
  • map:生成新数组(数组生成器,恰好可以遍历数组)
  • filter:生成新数组,过滤数组中某一些项
  • sort:排序
  • ……

二、函数式编程原理特点

1. 什么是函数式的原理

  • 原子组合变化 ⇒ 功能输入、输出保持不变,可以随意改变顺序

    eg:小鳄鱼洗澡:水源 ⇒ 组合(水管、弯头) ⇒ 花洒

2. 理论思想

  • 一等公民:函数

    1. 逻辑功能的落脚点—函数
    2. 实现函数 + 拼接流程
  • 声明式编程:声明需求

    1. 更贴近语言习惯
  • 惰性执行

    1. 无缝衔接
    2. 性能节约
    // 惰性函数
    const program = name => {
        if (name === 'progressive') {
            return program = () => {
                console.log('this is progressive');
            }
        }
        if (name === 'objective') {
            return program = () => {
                console.log('this is objective');
            }
        }
        return program = () => {
            console.log('this is function');
        }
    }
    
    program('progressive');
    program()
    

3. 无状态与无副作用

  • 无状态:数据不可变,不可操作改变源数据
  • 无副作用:函数内部不可直接对系统中任何参数变量改动

三、实际开发

1. 纯函数的改造

const _class = {
    name: "objective"
}

// 函数内部引用了外部变量,违反无状态
const score = str => _class.name + ':' + str;

// 修改传入的参数,违反无副作用
const changeClass = (obj, name) => obj.name = name;

changeClass(_class, 'functional');
score('good');

import _ from 'loadash'
const _class2 = {
    name: 'objective'
}

const score2 = (obj, str) => _.deepClone(obj).name + ':' + str;

const changeClass2 = (obj, name) => ({
    ...obj,
    name
});

changeClass2(_class, 'functional');
score(_class, 'good');

2. 流水线的组装

  • 加工:科里化

    意义:实现单个加工的输入输出单值化 ⇒ 单元函数

    • 面试题:手写构造可拆分传参的累加函数

      1. 构造科里化的结构
      2. 输入 处理外层的arguments ⇒ 类数组处理
      3. 传入参数无限扩展 ⇒ 内层递归 ⇒ 返回递归函数本身
      4. 主功能区
      5. 输出 从函数到产出 toString的替换
    const add = function () {
        // input 
        let args = Array.prototype.slice.call(arguments);
    
        // 内层函数构建
        let inner = function () {
            args.push(...arguments);
            return inner;
        }
    
        // 主功能区
        inner.toString = function () {
            return args.reduce((prev, cur) => {
                return prev + cur;
            })
        }
    
        return inner;
    }
    
    add(1)(2)(3).toString();
    
    // 如何调原来的toString => .call
    
  • 流水线—组装函数

    const compose(f, g) => x => f(g(x));
    
    const sum1 = x => x + 1;
    const sum2 = x => x + 2;
    
    const sum12 = compose(sum2, sum1);
    sum12(1);
    
  • 实际实现使用

    // 命令式
    trim(reverse(toUpperCase(map(arr))));
    
    // 面向对象
    arr.map().toUpperCase().reverse().trim()
    
    // 函数式
    const result = compose(trim, reverse, toUpperCase, map)
    

四、函子

// 一封情书
class Mail {
    constructor(content) {
        this.content = content;
    }
    map(fn) {
        return new Mail(fn(this.content));
    }
}

// 1. 小李写情书
let mail1 = new Mail('love');

// 2. 小张读信
let mail2 = mail1.map(function (mail) {
    return read(mail);
})

// 3. 小张担心泄漏信息,涂抹了信件内容
let mail3 = mail2.map(function (mail) {
    return burn(mail);
})

// 4. 八卦的人查看
mail3.map(function (mail) {
    return check(mail);
})

new Mail('love').map(read).map(burn).map(read);

Functor 函子遵守了一些特定规则的容器或数据协议

具有一个通用的map方法,返回新实例

具有结合外部的运算能力 ⇒ 可在管道中处理不同层级又很纯净的单元操作