65.9K
CodeProject 正在变化。 阅读更多。
Home

一个 JavaScript 函数式编程示例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (4投票s)

2015年3月13日

CPOL

5分钟阅读

viewsIcon

13578

downloadIcon

128

使用 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)
    将一个函数应用于初始参数和数组的第一个元素,然后递归地应用于数组的其余部分。

计划分三个阶段创建一个函数

  1. 创建一个函数,该函数从输入数据数组的元素中的字段返回 td 元素的数组。
  2. 然后创建一个函数,该函数返回包含来自 1) 的 td 元素的 tr 元素。
  3. 最后,创建一个函数,将来自 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 保持不变。

© . All rights reserved.