08月07, 2022

JS进阶(7)--高阶函数(4)--柯里化

骚操作:柯里化

函数柯里化(currying)的概念最早由俄国数学家 Moses Schönfinkel 发明,而后由著名的数理逻辑学家 Haskell Curry 将其丰富和发展,currying 由此得名。

1. 柯里化函数介绍

柯里化(currying)又称部分求值。一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

比如,有一个函数f(x, y, z) = (x + y) * z,若固定了 x = 2,你将通过柯里化函数得到一个新的函数,这个新的函数是g(y, z) = (2 + y) * z

假设,我们已经做好了一个柯里化函数,名叫 curry,我们可以这样来调用它:

/**
 * f(x, y, z)
 */
function f(x, y, z) {
    return (x + y) * z;
}

// 通过柯里化函数,固定函数 f 的第一个参数 x=2,得到一个新函数 g(y, z)
const g = curry(f, 2);
console.log(g(3, 5)); // (2+3)*5 输出:25
console.log(g(6, 4)); // (2+6)*4 输出:32

// 通过柯里化函数,固定函数f的前两个参数 x=2 y=3,得到一个新函数 k(z)
const k = curry(f, 2, 3);
console.log(k(5)); // (2+3)*5 输出:25
console.log(k(4)); // (2+3)*4 输出:20

相信通过上面的这个例子,能让你理解柯里化函数的作用,再说一遍:如果你固定了某些参数,你将得到接受余下参数的一个函数

也就是说,柯里化函数调用后,得到的是一个函数,我们姑且叫它返回函数,在调用返回函数的时候,它将判断当前的参数和之前被柯里化函数固定的参数拼起来之后,是否达到了原本函数的参数个数,如果是,则执行原本的函数,得到结果;如果没有达到,则要继续调用柯里化函数来固定目前的参数。

下面我们来书写这个柯里化函数,具体代码如下:

/**
 * 柯里化函数
 * @param {function} func 最终要执行的函数
 * @param {*} args 要固定的参数
 */
function curry(func, ...args) {
    // 返回一个接收剩余参数的函数
    return function (...inArgs) {
        // 将固定的参数和剩余参数拼接
        const allArgs = args.concat(inArgs);
        if (allArgs.length >= func.length) {
            // 若拼接后的参数集 大于等于 原本函数的参数数量,则执行原本的函数
            return func(...allArgs);
        }
        else{
            // 否则,继续柯里化,固定目前的参数
            return curry(func, ...allArgs);
        }
    }
}

调用柯里化函数后得到的是一个函数,执行该函数后,是否要得到结果,完全取决于你的参数够不够。

2. 柯里化函数应用场景

有了柯里化函数后,我们可以利用它简化一些操作,比如,向父元素添加子元素。

<body>
    <div id="container"></div>
    <script>
        /**
         * 向父元素添加子元素
         * @param {object} parent 父元素的dom对象
         * @param {string} dom 子元素的dom名称
         * @param {*} attributes 子元素的属性
         * @param {*} styles 子元素的样式
         * @param {*} innerHtml 子元素的内容
         */
        function curry(func, ...args) {
            //返回一个接收剩余参数的函数
            return function (...inArgs) {
                //将固定的参数和剩余参数拼接
                const allArgs = args.concat(inArgs);
                if (allArgs.length >= func.length) {
                    //若拼接后的参数集 大于等于 原本函数的参数数量,则执行原本的函数
                    return func(...allArgs);
                } else {
                    //否则,继续柯里化,固定目前的参数
                    return curry(func, ...allArgs);
                }
            }
        }

        function addChild(parent, dom, attributes, styles, innerHtml) {
            const newDom = document.createElement(dom); //创建子元素的dom
            for (const key in attributes) {
                newDom.setAttribute(key, attributes[key]); //为子元素添加属性
            }
            for (const key in styles) {
                newDom.style.setProperty(key, styles[key]); //为子元素添加样式
            }
            newDom.innerHTML = innerHtml; //设置子元素的内容
            parent.appendChild(newDom); //将子元素添加至父元素中
        }
        //向id为container的div中添加子元素

        //固定父元素的参数
        const create = curry(addChild, document.getElementById("container"));
        //继续固定参数,子元素为div,无属性
        const createDiv = create("div", {});
        //继续固定参数,子元素为红色的div
        const createRedDiv = createDiv({
            background: "red",
            width: "100px",
            height: "100px"
        });
        //加入3个红色的div,内容各不相同
        createRedDiv("red div 1");
        createRedDiv("red div 2");
        createRedDiv("red div 3");

        //加入3个蓝色的div,内容各不相同
        //继续固定参数,子元素为蓝色的div
        const createBlueDiv = createDiv({
            background: "blue",
            width: "200px",
            height: "200px"
        });
        createBlueDiv("blue div 1");
        createBlueDiv("blue div 2");
        createBlueDiv("blue div 3");
    </script>
</body>

本文链接:http://www.yanhongzhi.com/post/js_ap_16.html

-- EOF --

Comments