骚操作:柯里化
函数柯里化(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>
Comments