Javascript函数编程的深入探讨

有时候,优雅的实现是一种功能,不是方法,不是一个类,它不是一个框架,它只是一个函数。
约翰·卡马克,这个游戏毁灭战士的首席程序员




函数式编程是关于如何突破的问题转化为一系列的功能。正常情况下,功能是联系在一起的,嵌套的来回传递,作为一流的公民。如果你使用的框架如jQuery或Node.js,你应该使用这些技术,但是你不知道它。

我们从Javascript的一个小尴尬开始。

假设我们需要一个将分配给一个公共对象的值列表。这些对象可能包含任何内容:数据、HTML对象等等。




VaR
obj1 = { 1 }值,
obj2 = { 2 }值,
OBJ3 = { 3 }值;
var值{ };
功能积累(obj){
Values.push(obj值);
}
积累(obj1);
积累(obj2);
console.log(值); / /输出:{ obj1.value obj2值}。



这段代码可以使用但不稳定。任何代码都可以在没有累积()函数的情况下改变值对象。

但是如果变量在函数中声明,他将不会被任何恶意代码改变。




功能accumulate2(obj){
var值{ };
Values.push(obj值);
返回值;
}
console.log(accumulate2(obj1)); / /返回:{以此值}。
console.log(accumulate2(obj2)); / /返回值} { obj2。
console.log(accumulate2(OBJ3)); / /返回:{ OBJ3值}。



没有办法!返回最后一个对象的值。

我们可以通过在第一个函数内嵌套一个函数来解决这个问题。




无功valueaccumulator =功能(obj){
var值{ }
var =函数(){()
Values.push(obj值);
};
积累();
返回值;
};



但问题仍然存在,我们现在无法访问累积函数和变量值。

我们需要的是一个自调用函数。

自调用函数和闭包

如果我们可以返回一个可以返回到值数组的函数表达式呢函数中声明的变量可以由函数中的所有代码访问,包括自调用函数。

通过使用自动呼叫功能,前面的尴尬消失了。




无功valueaccumulator =函数(){()
var值{ };
VAR积累=功能(obj){
如果(obj){
Values.push(obj值);
返回值;
{人}
返回值;
}
};
返回积累;
};
这允许我们这样做:
VaR蓄电池= valueaccumulator();
蓄电池(obj1);
Accumulator (obj2);
console.log(累加器());
输出: / { obj1.value,obj2值}。
valueaccumulator = >
值= }
(obj)->
values.push obj.value如果obj
价值观



这些都是关于作用范围的。变量值在内部函数中可见,累积(),甚至当外部代码调用函数时,这就是所谓的壁橱。

Javascript中的闭包是函数可以访问父作用域的函数,即使父函数已经执行了。

闭包是所有函数语言的一个特性,传统的命令语言没有闭包。

高阶函数

自调用函数实际上是高阶函数的一种形式,高阶函数是用其他函数或函数的一个函数来输入的函数。

在传统的编程中,高阶函数是不常见的。当命令程序员使用循环迭代数组时,函数程序员使用完全不同的实现方式。

这是函数式编程的核心思想,高阶函数具有将对象的逻辑转换为函数的能力。

在Javascript中,函数作为第一类公民,这是经典的功能如方案和Haskell一样。这听起来可能有点奇怪,但实际意义是函数作为一种基本类型,如数字和物体。如果数量和对象可以通过来回,该功能可以。

来看看。现在使用的valueaccumulator最后一节()与高阶函数:

foreach / /(使用)来遍历数组,和回调函数的调用accumulator2每个元素

无功accumulator2 = valueaccumulator();

对象= { var obj1,obj2,OBJ3 }; / /这个数组可以是伟大的

objects.foreach(accumulator2);

console.log(accumulator2());

纯函数

纯函数返回的结果只与传入参数有关,外部变量和全局状态不在这里使用,也没有副作用,换句话说,不能改变引入的变量作为输入,因此程序只能使用纯函数返回的值。

用一个数学函数给出了一个简单的example.math.sqrt(4)将总是返回到2没有使用任何隐藏的信息,如设置或状态,不产生任何副作用。

纯函数是数学函数的真实推导,是输入和输出之间的关系,它们简单易用,因为纯函数是完全独立的,所以它们更适合反复使用。

给出了比较纯函数和纯函数的例子。




打印到中央屏幕的功能信息。
无功printcenter =功能(STR){
var elem = document.createelement(div);
elem.textcontent = str;
elem.style.position =绝对的;
elem.style.top = window.innerheight / 2 +PX;
elem.style.left = window.innerwidth / 2 +PX;
document.body.appendchild(元);
};
PrintCenter('Hello World);
做同样的事情/纯函数
无功printsomewhere =功能(STR,高度,宽度){
var elem = document.createelement(div);
elem.textcontent = str;
elem.style.position =绝对的;
elem.style.top =高度;
elem.style.left =宽度;
返回元素;
};
document.body.appendchild(
printsomewhere(你好世界,
window.innerheight / 2)+ 10 +PX
window.innerwidth / 2)+ 10 +PX)))



纯函数依赖于窗口对象的状态来计算宽度和高度,而自足的纯函数要求这些值作为参数传递,实际上它允许信息在任何地方打印,这使得函数更有用。

一个非纯粹的功能似乎是一个更容易的选择,因为它实现了一个额外的元素本身,而不是返回元素。纯粹的功能printsomewhere()返回的值将与其他功能的编程技术相结合更好的性能。




VaR的消息= {嗨,你好,'sup ',' ','hola};
Messages.map(函数(S,I){
返回printsomewhere(S,100 *我* 10, 100 *我* 10);
})。ForEach(功能(元){ {)
document.body.appendchild(元);
});



当一个函数是纯的,也就是说,它不依赖于状态和环境,我们不必在实际计算时管理它。

匿名函数

将函数作为第一类对象的另一个优点是匿名函数。

顾名思义,匿名函数是一个没有名字的函数,它只允许在站点上定义临时逻辑。通常,这样做的好处是方便:如果一个函数只使用一次,就不必浪费变量名。

下面是一些匿名函数的例子:




写匿名函数的标准方法
函数(){
返回Hello World
};

可以将匿名函数分配给变量。
无功非=功能(x,y){
返回x y
};

匿名函数代替命名回调函数,这是一种更常见的匿名函数。
setInterval(){()函数(
console.log(新的日期()GetTime())
},1000);
输出:1413249010672, 1413249010673, 1413249010674,…

如果没有在匿名函数中包含它,他将立即被执行,
返回一个未定义的回调函数:
setInterval(console.log(新的日期()GetTime()()),1000)
1413249010671 输出:





下面是匿名和高阶函数的组合示例。




功能权限(x){
函数返回(y){
是一个匿名函数!
math.pow(x,y)返回;
};
}
poweroftwo =力量(2);
console.log(poweroftwo(1)); / / 2
console.log(poweroftwo(2)); / / 4
console.log(poweroftwo(3)); / / 8
powerofthree =力量(3);
console.log(powerofthree(3)); / / 9
console.log(powerofthree(10)); / / 59049



该函数返回这里不需要命名的,它可以在任何地方使用外部力量()函数,这是一个匿名函数。

你还记得蓄能器的功能吗它可以用匿名函数重写。




VaR
obj1 = { 1 }值,
obj2 = { 2 }值,
OBJ3 = { 3 }值;

var值=(函数(){())
匿名函数
var值{ };
返回功能(obj){
具有匿名函数!
如果(obj){
Values.push(obj值);
返回值;
{人}
返回值;
}
}
}());让它执行
console.log(值(obj1)); / /返回:{ obj值}。
console.log(值(obj2)); / /返回:{ obj.value obj2值}。
obj1 = { 1 }值:
obj2 = { 2 }值:
OBJ3 = { 3 }值:

值=做
valuelist = { }
(obj)->
valuelist.push obj.value如果obj
valuelist
console.log(值(obj1)返回:{ obj值} #);
console.log(值(obj2)#返回:{ obj.value,obj2。值});




这是伟大的uff01a高阶匿名纯功能。我们是多么的幸运吗实际上,除此之外,还有一个自执行结构(函数){(});函数后面的括号允许函数立即执行。

匿名函数不仅仅是语法糖,他们是lambda演算的化身。请听我说…lambda演算出现很久以前当计算机和计算机语言被发明的。这只是一个数学概念的功能研究。不寻常的是,尽管它只定义了三种表达方式:变量的引用,函数调用,和匿名函数,它被发现是图灵完整性。今天,lambda演算是所有语言功能的核心,包括Javascript。

由于这个原因,匿名函数通常称为lambda表达式。

匿名函数的一个缺点是在调用堆栈中很难识别,这会给调试带来一些困难。

方法链

在Javascript中,将方法链接在一起是很常见的。如果使用jQuery,就应该使用这种技术。

此技术用于简化将多个函数依次应用于对象的代码。




每一个函数都需要一行调用,如……
ARR = { 1, 2, 3,4 };
arr.reverse arr1 =();
arr1.concat arr2 =({ 5, 6 });
ARR3 = arr2.map(数学。:);

/……把它们排成一行
console.log({ 1, 2, 3,4 }。反()。Concat({ 5, 6 })。图(数学。:));
括号可以解释正在发生的事情。
console.log (((((((((( { 1, 2, 3,4 }),反()),Concat({ 5, 6 }))。图(数学。sqrt)));




这是唯一有效的函数时,目标对象的方法。如果你想创建自己的功能,例如,如果你想把两个阵列的邮编,你必须声明它的array.prototype对象的一个成员。

array.prototype.zip =功能(arr2){



}

我们可以写以下内容

Arr.zip({ 11,12,13,14)。地图(功能(N){ return n×2 });

输出:2, 22, 4,24, 6, 26,8, 28

递归

递归应该是最著名的函数编程技术,它是一种调用自己的函数。

当函数调用自身时,有时会发生奇怪的事情,其性能是一个循环,相同的代码多次执行,也是一个函数堆栈。

递归函数的使用必须非常小心,以避免一个无限循环(假设是无限递归),就像一个循环,必须有一个停止条件,这称为基本情况。

下面是一个例子




函数(n){
如果(n<0){
/基线场景
return'hello;
{人}
递归实例
返回富(N - 1);
}
}
console.log(foo(5));



原始文本中的代码不正确,递归函数的函数调用缺少返回,最终导致函数执行。

递归和循环可以互相转换,但递归算法往往更合适,甚至更必要,因为有些情况很难循环。

一个明显的例子是遍历树。




无功getleafs =功能(节点){
如果(node.childnodes.length = = 0){
基础
返回node.innertext;
{人}
递归案例:
返回node.childnodes.map(getleafs);
}
}



分而治之

递归不仅是一种有趣的替换while循环的方法,还有一种称为分而治之的算法,递归地将问题分为更小的情况,直到它足够小以解决。

在历史上,有一个欧几里德算法用来寻找两个数的最大公分母。




功能(A,B){ GCD
如果(b = 0){
/基线场景(规则)
返回一个;
{人}
递归案例(min)
返回最大公约数(B,一% B);
}
}

console.log(GCD(12,8));
console.log(gcd(100,20));

GCB =(a,b)->如果B是0那么其他GCB(B,一% B)




从理论上讲,分而治之是伟大的,但它在现实中有用吗当然 uff01sorting Javascript功能不是很好,它不但取代原有的阵列,即数据是不变的,它是不可靠的,灵活的,我们可以做的更好,通过分而治之。

完整的实现代码大约有40行,这里只显示伪代码:




VaR归并=功能(ARR){
如果(arr.length<2){
基本情况:数组只有0个或1个元素没有排序。
返回的项目;
{人}
情况:数组递归排序、合并、拆分
VaR中= math.floor(arr.length / 2);

VaR左= mergeSort(arr.slice(0中));
VaR(arr.slice右= mergeSort(中));
治疗
合并是一个辅助函数,返回一个新数组,它将两个数组连接在一起。
返回合并(左,右);
}
}



用分运行的思想来排序的一个更好的例子是快速行,只有13行代码使用Javascript。请参考我以前的博客,优雅的函数式编程语言

惰性的评价

延迟求值也称为非严格求值,是根据需求和延迟执行来调用的。它是一种评价策略来计算一个函数的结果直到需要它的时候,它的功能规划是特别有用的。例如,一行代码是X = func()的返回值,并通过调用函数获得()函数将被分配到X,但X等于不重要一开始,直到你需要使用X调用函数()当你需要使用X是一种惰性的评价。

这种策略可以显著提高性能,尤其是当使用这些功能的程序员的程序流程技术的阵列的方法链。一个惰性评价激励的优点是使无限序列的可能。因为没有什么真的需要真的算到不能延迟。可以这样:




javascript伪代码理想:
无功infinatenums =范围(1到无穷大);
无功tenprimes = infinatenums.getprimenumbers()第一(10);



这为许多可能性打开了大门,例如异步执行、并行计算和组合,它们只列出一点点。

然而,还有另外一个问题。Javascript本身不支持惰性评估,也就是说,有一个函数库允许Javascript模拟惰性评估。这是第三章的主题。