[JS系列]:函数柯里化、递归

902 阅读3分钟

1. 函数柯里化

函数柯里化:是把接受多个参数的函数转变为接受一个参数的函数。 大家好,我是EchoTX,这篇文章主要记录对柯里化函数的理解与实现。实现方式采用递归算法《红宝书》---函数通过名称调用自己。个人感觉:不考虑性能,大部分项目中遇到的算法问题都可以采用递归的方式来解决。这里记录柯里化递归实现,以及一些使用递归解决的问题。

涉及的知识点:

  • function
  • arguments
  • this
  • 闭包

1.1 加法器基础

需求1: 实现一个加法器

arguments对象不是一个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。 --> 来自MDN

//1. 单参数
function add(a, b){
    return a + b;
}
add(1, 2);
//2. 多参数
function add(...arg){
    return arg.reduce((sum, num) => sum + num);
}
add(1, 2, 3);
//3. 使用arguments
function add(){
    var args = [].slice.call(arguments)
    return args.reduce((sum, num) => sum + num);
}
add(1, 2, 3);

调用 add(1, 2) 算出 1 + 2 的结果为:3。

: 多参数使用 ...arg 展开数组其实是实例化了一个Array,而arguments是一个类数组。

通常使用[...arguments]、[].slice.call(arguments)、Array.from(arguments)的方法将类数组转换为Array

1.2 柯里化加法器

需求2: 柯里化实现加法器

//1. 两个参数
function add(a){
    return function(b) {
        return a + b;
    }
}
add(1)(2)
//2. 三个参数
function add(a){
    return function(b) {
        return function(c) {
            return a + b + c
        }
    }
}
add(1)(2)(3)

问题:需求1实现的方式直接比需求2方式实现简单多了 使用柯里化感觉更麻烦了呢,那么需求3来了。

需求3: 加法器计数,具有存储功能。

//1. 两个参数
function add(a){
    return function(b) {
        return a + b
    }
}
var IncreaseFirst = add(1)
var IncreaseSecond = add(6)
IncreaseFirst(2) // 3
IncreaseSecond(2) // 8

//1. 三个参数
function add(a) {
    return function(b) {
        return function(c) {
            return a + b + c;
        };
    };
}
var IncreaseFirst = add(1); //存第一个
var IncreaseSecond = IncreaseFirst(1); //存第二个
var IncreaseThird = IncreaseSecond(1); //存第三个
console.log(IncreaseThird); //3

上面的例子就利用了柯里化 闭包的性质,保存了参数,最后需要的时候再进行调用。

1.3 柯里化实现

需求4: 从上面已经认识到了函数的柯里化,那么请实现一个简单版的柯里化函数。

function currying(fn) {
    var count = arguments.length;
    return function curried() {
        var _this = this
        var args = [...arguments];
        if (count < 0) {
            return fn.apply(_this, args);
        }
        count--;
        return (arg) => {
            return curried.apply(_this, [...args, arg])
        };

    };
}
var fun = currying(function add(a, b, c) {
    console.log([a, b, c]);
});
fun(2)(1)(3);

2. 递归练习1: 斐波那契数列实现

斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13, 特别指出:第0项是0,第1项是第一个1。从第三项开始,每一项都等于前两项之和。

//代码实现
var fib = function(n) {
    if(n < 2) return n;
    return fib(n - 1) + fib(n - 2);
};
fib(7)

3. 递归练习2: 树型化结构扁平

需求1: 将以下树形数据进行扁平化

var treeList = [
    {
        id: 1,
        name: 'A',
        children: [
            {
                id: 2,
                name: 'B',
                children: [
                    {
                        id: 8,
                        name: 'H'
                    }
                ]
            },
            {
                id: 3,
                name: 'C',
            }
        ]
    },{
        id: 4,
        name: 'D',
        children: [
            {
                id: 5,
                name: 'E',
            },
            {
                id: 6,
                name: 'F',
            }
        ]
    },{
        id: 7,
        name: 'G',
    }
]

3.1 初步实现

function treeToList(data, treeList = []){
    var list = treeList
    for(let k in data){
        var {children, ...other} = data[k]
        list.push(other)
        if(children){
            treeToList(children, list)
        }
    }
    return treeList
}
var result = treeToList(treeList)

image.png

从上面代码及效果图 可见已经实现了树型化数据扁平了。

仔细看看,好像总觉得缺少了什么

最后发现: 数据是扁平了但是数据之间没有关联。(树型化数据扁平化 -- 扁平数据树型化 可以相互转换的)

3.2 最终实现

从初步实现中,缺少子级与父级的关系,那么可以声明一个pid 和父级 id相对应的关系进行数据扁平化。 如下:

function treeToList(data, treeList = [], pid = 0){
    var list = treeList
    for(let k in data){
        var {children, ...other} = data[k]
        if(children){
            other.pid = pid
            list.push(other)
            treeToList(children, list, data[k].id)
        }else{
            var _children = JSON.parse(JSON.stringify(data[k]))
            _children.pid = pid
            list.push(_children)
        }
    }
    return treeList
}
var result = treeToList(treeList)

image.png

4. 递归练习3: 扁平化数据树型化

上述已经讲到了树型化结构扁平化,这里就再利用递归的方法再实现实现扁平化数据转为树型化吧

4.1 递归实现

function listToTree(data){
    var pNode = data.filter(item => item.pid === 0); //求出最外层的父节点
    var cNode = data.filter(item => item.pid !== 0); //求出其余子节点
    // pid 和 id的关系进行关系匹配
    toTree(pNode, cNode)
    return pNode
    function toTree(parent, child){
        parent.map(pItem => {
            child.map(cItem => {
                if(cItem.pid === pItem.id){
                    var _child = JSON.parse(JSON.stringify(child))
                    _child.splice(0, 1)
                    toTree([cItem], _child)
                    if(pItem.children){
                        pItem.children.push(cItem)
                    }else{
                        pItem.children = [cItem]
                    }
                }
            })
        })
    }
}
listToTree(list)

4.2 实现思路

通过上面代码即可实现扁平化数据转树型化数据。实现思路:

  1. 将祖先节点和子节点区分开
  2. 通过子节点的pid和父节点的id进行匹配
  3. 父节点没有children键名,就进行添加,有的话就push
  4. cItem不是数组递归和赋值时 需变为数组
  5. 通过splice删除已确认关系的元素, 减少循环次数

5 写在最后

快乐学习每一天