一个 JavaScript 函数式编程示例






4.67/5 (4投票s)
使用 JavaScript 中的函数式编程技术向表格添加行
引言
JavaScript 拥有几项特性,使其能够使用函数式编程的构造。这些构造可以通过逐步构建函数来派生函数。这使得在函数开发的每个步骤中都能对其进行测试。本技巧将通过一个简单的任务来演示这一点:创建一个函数,该函数以对象数组作为输入,并将对象中的数据附加到表中。
Using the Code
演示网页中有用于测试本技巧中代码的按钮。下载演示页面和 FJS 脚本文件。
目标是创建一个函数,用于将数组中的数据追加到表中。该函数将由基本的 JavaScript 函数和一些也用 JavaScript 实现的函数式编程构造组成。此过程允许测试函数中的每个部分,然后将这些部分组合在一起。
本项目使用了一个名为FJS.js的库。这是某些基本函数式编程构造的实现。可以使用任何函数式编程库代替。
首先,一点函数式编程
Map(f,[a1, a2, ..]) = [f(a1), f(a2)]
将函数 f 应用于数组的元素以创建另一个数组。Compose(f1,f2)(arg) = f1(f2(arg))
由函数f1,f2
(可以使用两个以上的函数)的组合创建函数。LCurry(f,arg1)(arg2) = f(arg1,arg2)
由f
和参数arg1
创建一个函数。调用此新函数时,它与f
使用arg1
和arg2
相同。Prop(propertyname)(object) = object.propertyname
一个返回对象属性值的函数。Onto([f1,f2,...])(arg) = [f1(arg), f2(arg), ...]
将函数数组[f1,f1,...]
应用于参数以创建数组。Foldl(f,initial,[arg1,arg2,...]) = f(f(initial,arg1),arg2)
将一个函数应用于初始参数和数组的第一个元素,然后递归地应用于数组的其余部分。
计划分三个阶段创建一个函数
- 创建一个函数,该函数从输入数据数组的元素中的字段返回
td
元素的数组。 - 然后创建一个函数,该函数返回包含来自 1) 的
td
元素的tr
元素。 - 最后,创建一个函数,将来自
2
) 的tr
元素追加到表中,并将该函数应用于数据数组的所有元素。
构建函数
为我们将需要的函数式编程定义一些别名。
var LCurry = FJS.LCurry;
var Prop = FJS.Prop;
var Compose = FJS.Compose;
var OnTo = FJS.OnTo;
var Foldl = FJS.Foldl;
var Map = FJS.Map;
将作为输入的数组是
var A = [{title:"On the Revolutions of Heavenly Spheres",author:"Nicolaus Copernicus"},
{title:"Dialogues Concerning the Two Sciences",author:"Galileo Galilei"},
{title:"The Geometry",author:"Rene Descartes"}];
选择一个索引进行测试,稍后将其删除。
var i = 2;
定义一些基本的支持函数
function AppendTo(Parent,Child)
{
Parent.appendChild(Child);
return Parent;
}
function TDNode(innerHTML)
{
var tdele = document.createElement("td");
tdele.innerHTML = innerHTML;
return tdele;
}
function TRNode(TREle,TDEle)
{
if (TREle === null)
TREle = document.createElement("tr");
TREle.appendChild(TDEle);
return TREle;
}
定义一些将保存最终函数的变量。
var Cells;
var TR;
var TableAppend;
现在,逐步派生程序,以便可以测试每个步骤。每个步骤都将分配给一个以“step”开头的变量。这样,就可以在这些行上设置断点来检查程序在该阶段的输出。稍后,可以删除这些行。
对于 1)
var TargetTable = document.getElementById("TargetTable1");
使用 TDNode
和 Prop
函数以通常的 JavaScript 方式创建带有 TDNode
和 Prop
函数的 td
元素。然后,这以 Compose
函数的形式编写为 F(A[i])。
var step1 = TDNode(Prop("title")(A[i]));
var step2 = Compose(TDNode,Prop("title"))(A[i]);
以类似的方式为对象中的其他字段创建函数。将它们放在一个数组中,并使用 Onto
将函数数组应用于 A[i]
。这将创建一个 td
元素的数组。然后,使用 LCurry
将 Onto
重写为 F(A[i])
的形式。然后这就是 Cells
函数。
var step3 = OnTo([Compose(TDNode,Prop("title")), Compose(TDNode,Prop("author"))],A[i]);
var step4 = LCurry(OnTo,[Compose(TDNode,Prop("title")), Compose(TDNode,Prop("author"))])(A[i]);
Cells = LCurry(OnTo,[Compose(TDNode,Prop("title")), Compose(TDNode,Prop("author"))]);
对于 2),我们想派生 TR
函数,该函数创建一个 tr
元素并将 Cells
中的数组追加到其中。这是通过递归地将 TRNode
函数(先前定义)应用于 Cells
返回的数组来完成的,然后使用 LCurry
重写该数组以“删除” TRNode
函数并进行组合。然后,使用 Compose
获得所需的函数。
var step5 = Foldl(TRNode,null,Cells(A[i]));
var step6 = LCurry(Foldl,TRNode,null)(Cells(A[i]));
var step7 = Compose(LCurry(Foldl,TRNode,null),Cells)(A[i]);
TR = Compose(LCurry(Foldl,TRNode,null),Cells);
最后对于 3),使用 AppendTo
函数将 TR
中的行元素追加到表中。然后使用 LCurry
将 AppendTo
变成一个函数。然后这是 LCurry
和 TR
的组合,所以使用 Compose
将函数转换为 F(A[i])
的形式。使用 Map
将此函数应用于 A
的每个元素。再次,LCurry
将方程置于所需格式。因此,最终产品 TableAppend
是
var step8 = AppendTo(TargetTable,TR(A[i]));
var step9 = LCurry(AppendTo,TargetTable)(TR(A[i]));
var step10 = Compose(LCurry(AppendTo,TargetTable),TR)(A[i]);
var step11 = Map(Compose(LCurry(AppendTo,TargetTable),TR),A);
var step12 = LCurry(Map,Compose(LCurry(AppendTo,TargetTable),TR))(A);
TableAppend = LCurry(Map,Compose(LCurry(AppendTo,TargetTable),TR));
删除所有步进线可得到最终产品
Cells = LCurry(OnTo,[Compose(TDNode,Prop("title")), Compose(TDNode,Prop("author"))]);
TR = Compose(LCurry(Foldl,TRNode,null),Cells);
TableAppend = LCurry(Map,Compose(LCurry(AppendTo,TargetTable),TR));
函数式编程的一个优点是 TableAppend
可以通过替换 TR
和 Cells
来写成一个函数。
TableAppend = LCurry(Map,Compose(LCurry(AppendTo,TargetTable),
Compose(LCurry(Foldl,TRNode,null),LCurry(OnTo,[Compose(TDNode,Prop("title")),
Compose(TDNode,Prop("author"))]))));
这相当复杂,将其保留在三个以前的函数中可以使其更易于阅读。
为表格添加属性
现在,我们将向函数添加属性对象。这些属性将应用于从 A
数组创建的 td
元素。这只需要更改 Cells
函数。
首先,更改 TDNode
函数以获取属性对象参数,并将对象的属性作为 TD
节点的属性应用。
function TDNode2(AttrObject,innerHTML)
{
var tdele = document.createElement("td");
tdele.innerHTML = innerHTML;
var p;
for (p in AttrObject) {
tdele.setAttribute(p,AttrObject[p]);
}
return tdele;
}
现在,为标题和作者创建属性对象。这些对象仅使用 style
属性,但任何属性都可以放入这些对象中。
var AttrTitle = {style:"background-color:LightBlue"};
var AttrAuthor = {style:"background-color:LightGreen"};
然后,重写前面的函数如下:使用 LCurry
然后使用 Compose
形成创建带有 attributes
对象的 td
节点的函数。
var step13 = TDNode2(AttrTitle,Prop("title")(A[i]));
var step14 = LCurry(TDNode2,AttrTitle)(Prop("title")(A[i]));
var step15 = Compose(LCurry(TDNode2,AttrTitle),Prop("title"))(A[i]);
然后将其插入 Cells
定义中的数组中,得到
var step16 = LCurry(OnTo,[Compose(LCurry(TDNode2,AttrTitle),Prop("title")),
Compose(LCurry(TDNode2,AttrAuthor),Prop("author"))])(A[i]);
Cells = LCurry(OnTo,[Compose(LCurry(TDNode2,AttrTitle),Prop("title")),
Compose(LCurry(TDNode2,AttrAuthor),Prop("author"))]);
TR
和 TableAppend
保持不变。