> 웹 프론트엔드 > JS 튜토리얼 > JavaScript 함수형 프로그래밍 실습(IBM 제공)_javascript 기술

JavaScript 함수형 프로그래밍 실습(IBM 제공)_javascript 기술

PHP中文网
풀어 주다: 2016-05-16 18:24:18
원래의
1151명이 탐색했습니다.

함수형 프로그래밍에 관해 사람들의 첫인상은 학문적이고 모호하다는 것입니다. 아마 엉성하고 엉성하며 신경증적인 대학 교수들만 사용하는 것 같습니다.

함수형 프로그래밍 입문
함수형 프로그래밍에 대해 말하면 사람들의 첫인상은 종종 학문적이고 모호하다는 것입니다. 아마도 헝클어진 머리, 헝클어진 외모, 심지어 A 프로그래밍을 가진 사람들에게만 해당될 것입니다. 일부 신경증적인 대학 교수들만이 사용하는 방법이다. 이는 역사상 특정 ​​단계에서는 사실일 수도 있지만, 최근에는 함수형 프로그래밍이 실제 응용에서 큰 역할을 했으며, 점점 더 많은 언어가 계속해서 클로저, 익명 함수 등과 같은 지원을 추가하고 있습니다. 어느 정도는, 함수형 프로그래밍은 점차적으로 명령형 프로그래밍을 "동화"합니다.
함수형 프로그래밍 아이디어의 기원은 수학자 Aaron Zoe Church가 나중에 람다 미적분학으로 알려진 문제의 계산 가능성에 대한 연구를 수행하던 1930년대로 거슬러 올라갑니다. 람다 미적분학의 핵심은 모든 것이 함수라는 것입니다. 함수는 다른 함수의 출력 또는/또는 입력으로 사용될 수 있으며, 일련의 함수 사용은 결국 표현식 체인을 형성할 수 있으며, 이 과정, 그것이 컴퓨팅의 본질이다.
그러나 이 아이디어는 당시의 하드웨어로는 실현하기 어려웠습니다. 역사는 결국 Church의 람다 이론과 유사한 또 다른 수학적 이론인 Turing Machine을 컴퓨팅 이론으로 선택했고, 결국 또 다른 과학자 Feng의 컴퓨터 아키텍처가 구현되었습니다. 하드웨어로. 최초의 컴퓨터는 폰 노이만의 프로그램 저장 구조였기 때문에 이 플랫폼에서 실행되는 프로그램도 C/Pascal과 같은 프로그래밍 언어 모두 이 시스템에 어느 정도 의존하고 있습니다.
1950년대 MIT 교수인 존 매카시(John McCarthy)가 폰 노이만 시스템 기계에 람다 이론을 성공적으로 구현하고 이를 LISP(LIST Processor)라고 명명한 이후 컴퓨터 분야에서 함수형 프로그래밍 언어가 활발해졌습니다. 과학.
함수형 프로그래밍 언어의 특징
함수형 프로그래밍 언어에서 함수는 일급 객체입니다. 즉, 함수는 다른 객체에 의존하지 않고 독립적으로 존재할 수 있습니다. 언어, 기능(메서드)은 객체에 첨부되며 객체의 일부입니다. 이 j는 나가는/들어오는 매개변수, 일반 변수 등과 같은 함수형 언어에서 함수의 일부 특수 속성을 결정합니다.
명령형 프로그래밍 언어와 달리 함수형 프로그래밍 언어에는 몇 가지 특별한 개념이 있으며 별도로 논의하겠습니다.
익명 함수
함수형 프로그래밍 언어에서는 함수가 이름으로 생략될 수 있습니다. , 익명 함수는 일반적으로 "무언가를 수행하는 코드 조각"을 의미합니다. 이 표현식은 어떤 일을 수행하기 위해 함수를 사용해야 하는 경우가 있기 때문에 많은 상황에서 유용합니다. 그러나 이 함수는 일시적일 뿐이므로 이를 위해 특별히 최상위 함수 개체를 생성할 이유가 없습니다. 예:

목록 1. 지도 함수

function map(array, func){ 
var res = []; 
for ( var i = 0, len = array.length; i < len; i++){ 
res.push(func(array[i])); 
} 
return res; 
} 
var mapped = map([1, 3, 5, 7, 8], function (n){ 
return n = n + 1; 
}); 
print(mapped);
로그인 후 복사

이 코드를 실행하면 다음이 인쇄됩니다.
2,4,6,8,9/ / 추가 배열 [1,3,5,7,8]의 각 요소에 1


map 함수 호출에 주의하세요. map의 두 번째 매개 변수는 함수입니다. function 이는 맵의 첫 번째 매개변수(배열) 각각에 영향을 미치지만 맵 외부의 코드에는 아무런 의미가 없을 수 있습니다. 따라서 이에 대한 함수를 구체적으로 정의할 필요가 없습니다. 익명 함수는 다음과 같습니다. 충분한.
커링
커링은 여러 매개변수를 받는 함수를 단일 매개변수(원래 함수의 첫 번째 매개변수)를 받고 나머지 매개변수를 반환하는 함수로 변환하는 기술이다. 결과를 반환하는 새로운 함수. 이 문장은 약간 복잡하므로 이해를 돕기 위해 예를 사용할 수 있습니다.
목록 2. 커링 함수

function adder(num){ 
return 
function (x){ 
return num + x; 
} 
} 
var add5 = adder(5); 
var add6 = adder(6); 
print(add5(1)); 
print(add6(1));
로그인 후 복사

결과는 다음과 같습니다.
6
7
더 흥미로운 점은 함수 추가기가 매개변수를 받아들이고 반환된 함수를 예상대로 호출할 수 있다는 것입니다. 변수 add5는 하나의 매개변수를 취하고 매개변수와 5의 합을 반환하는 adder(5)에 의해 반환된 함수를 보유합니다.
커링은 다음 섹션에서 볼 수 있듯이 DOM 콜백에 매우 유용합니다.
고차 함수
고차 함수는 함수를 더욱 추상화한 것입니다. 사실 익명 함수 섹션에서 언급한 맵 함수는 많은 경우에 고차 함수입니다. 이 함수는 함수형 프로그래밍 언어에 있습니다. map(array, func)의 표현은 func 함수가 배열의 각 요소에 적용되어 최종적으로 새로운 배열을 반환한다는 것을 보여줍니다. map은 array와 func의 구현에 대해 어떠한 사전 가정도 하지 않는다는 점에 유의해야 합니다. . 이므로 "고차" 함수라고 합니다.

목록 3. 고차 함수

function map(array, func){ 
var res = []; 
for ( var i = 0, len = array.length; i < len; i++){ 
res.push(func(array[i])); 
} 
return res; 
} 
var mapped = map([1, 3, 5, 7, 8], function (n){ 
return n = n + 1; 
}); 
print(mapped); 
var mapped2 = map(["one", "two", "three", "four"], 
function (item){ 
return "("+item+")"; 
}); 
print(mapped2);
로그인 후 복사

将会打印如下结果:
2,4,6,8,9
(one),(two),(three),(four)// 为数组中的每个字符串加上括号

mapped 和 mapped2 均调用了 map,但是得到了截然不同的结果,因为 map 的参数本身已经进行了一次抽象,map 函数做的是第二次抽象,高阶的“阶”可以理解为抽象的层次。
JavaScript 中的函数式编程
JavaScript 是一门被误解甚深的语言,由于早期的 Web 开发中,充满了大量的 copy-paste 代码,因此平时可以见到的 JavaScript 代码质量多半不高,而且 JavaScript 代码总是很飞动的不断闪烁的 gif 广告,限制网页内容的复制等联系在一起的,因此包括 Web 开发者在内的很多人根本不愿意去学习 JavaScript。
这种情形在 Ajax 复兴时得到了彻底的扭转,Google Map,Gmail 等 Ajax 应用的出现使人们惊叹:原来 JavaScript 还可以做这样的事!很快,大量优秀的 JavaScript/Ajax 框架不断出现,比如 Dojo,Prototype,jQuery,ExtJS 等等。这些代码在给页面带来绚丽的效果的同时,也让开发者看到函数式语言代码的优雅。
函数式编程风格
在 JavaScript 中,函数本身为一种特殊对象,属于顶层对象,不依赖于任何其他的对象而存在,因此可以将函数作为传出 / 传入参数,可以存储在变量中,以及一切其他对象可以做的事情 ( 因为函数就是对象 )。
JavaScript 被称为有着 C 语法的 LISP,LISP 代码的一个显著的特点是大量的括号以及前置的函数名,比如:

清单 4. LISP 中的加法
(+ 1 3 4 5 6 7)

加号在 LISP 中为一个函数,这条表达式的意思为将加号后边的所有数字加起来,并将值返回,JavaScript 可以定义同样的求和函数:

清单 5. JavaScript 中的求和

function sum(){ 
var res = 0; 
for ( var i = 0, len = arguments.length; i < len; i++){ 
res += parseInt(arguments[i]); 
} 
return res; 
} 
print(sum(1,2,3)); 
print(sum(1,2,3,4,6,7,8));
로그인 후 복사

运行此段代码,得到如下结果:
6
31

如果要完全模拟函数式编码的风格,我们可以定义一些诸如:
清单 6. 一些简单的函数抽象

function add(a, b){ return a+b; } 
function sub(a, b){ return a-b; } 
function mul(a, b){ return a*b; } 
function p(a, b){ return a/b; } 
function rem(a, b){ return a%b; } 
function inc(x){ return x + 1; } 
function dec(x){ return x - 1; } 
function equal(a, b){ return a==b; } 
function great(a, b){ return a>b; } 
function less(a, b){ return a<b; }
로그인 후 복사

这样的小函数以及谓词,那样我们写出的代码就更容易被有函数式编程经验的人所接受:
清单 7. 函数式编程风格

// 修改之前的代码 
function factorial(n){ 
if (n == 1){ 
return 1; 
} else { 
return factorial(n - 1) * n; 
} 
} 
// 更接近“函数式”编程风格的代码 
function factorial(n){ 
if (equal(n, 1)){ 
return 1; 
} else { 
return mul(n, factorial(dec(n))); 
} 
}
로그인 후 복사

闭包及其使用
闭包是一个很有趣的主题,当在一个函数 outter 内部定义另一个函数 inner,而 inner 又引用了 outter 作用域内的变量,在 outter 之外使用 inner 函数,则形成了闭包。描述起来虽然比较复杂,在实际编程中却经常无意的使用了闭包特性。
清单 8. 一个闭包的例子

function outter(){ 
var n = 0; 
return 
function (){ 
return n++; 
} 
} 
var o1 = outter(); 
o1();//n == 0 
o1();//n == 1 
o1();//n == 2 
var o2 = outter(); 
o2();//n == 0 
o2();//n == 1
로그인 후 복사

匿名函数 function(){return n++;} 中包含对 outter 的局部变量 n 的引用,因此当 outter 返回时,n 的值被保留 ( 不会被垃圾回收机制回收 ),持续调用 o1(),将会改变 n 的值。而 o2 的值并不会随着 o1() 被调用而改变,第一次调用 o2 会得到 n==0 的结果,用面向对象的术语来说,就是 o1 和 o2 为不同的 实例,互不干涉。
总的来说,闭包很简单,不是吗?但是,闭包可以带来很多好处,比如我们在 Web 开发中经常用到的:
清单 9. jQuery 中的闭包

var con = $("p#con"); 
setTimeout( function (){ 
con.css({background:"gray"}); 
}, 2000);
로그인 후 복사

上边的代码使用了 jQuery 的选择器,找到 id 为 con 的 p 元素,注册计时器,当两秒中之后,将该 p 的背景色设置为灰色。这个代码片段的神奇之处在于,在调用了 setTimeout 函数之后,con 依旧被保持在函数内部,当两秒钟之后,id 为 con 的 p 元素的背景色确实得到了改变。应该注意的是,setTimeout 在调用之后已经返回了,但是 con 没有被释放,这是因为 con 引用了全局作用域里的变量 con。
使用闭包可以使我们的代码更加简洁,关于闭包的更详细论述可以在参考信息中找到。由于闭包的特殊性,在使用闭包时一定要小心,我们再来看一个容易令人困惑的例子:
清单 10. 错误的使用闭包

var outter = []; 
function clouseTest () { 
var array = ["one", "two", "three", "four"]; 
for ( var i = 0; i < array.length;i++){ 
var x = {}; 
x.no = i; 
x.text = array[i]; 
x.invoke = function (){ 
print(i); 
} 
outter.push(x); 
} 
}
로그인 후 복사

上边的代码片段很简单,将多个这样的 JavaScript 对象存入 outter 数组:

清单 11. 匿名对象

{ 
no : Number, 
text : String, 
invoke : function (){ 
// 打印自己的 no 字段 
} 
}
로그인 후 복사

我们来运行这段代码:
清单 12. 错误的结果

clouseTest();// 调用这个函数,向 outter 数组中添加对象 
for ( var i = 0, len = outter.length; i < len; i++){ 
outter[i].invoke(); 
}
로그인 후 복사

出乎意料的是,这段代码将打印:
4
4
4
4
而不是 1,2,3,4 这样的序列。让我们来看看发生了什么事,每一个内部变量 x 都填写了自己的 no,text,invoke 字段,但是 invoke 却总是打印最后一个 i。原来,我们为 invoke 注册的函数为:

清单 13. 错误的原因

function invoke(){ 
print(i); }
로그인 후 복사

每一个 invoke 均是如此,当调用 outter[i].invoke 时,i 的值才会被去到,由于 i 是闭包中的局部变量,for 循环最后退出时的值为 4,因此调用 outter 中的每个元素都会得到 4。因此,我们需要对这个函数进行一些改造:

清单 14. 正确的使用闭包

var outter = []; 
function clouseTest2(){ 
var array = ["one", "two", "three", "four"]; 
for ( var i = 0; i < array.length;i++){ 
var x = {}; 
x.no = i; 
x.text = array[i]; 
x.invoke = function (no){ 
return 
function (){ 
print(no); 
} 
}(i); 
outter.push(x); 
} 
}
로그인 후 복사

通过将函数 柯里化,我们这次为 outter 的每个元素注册的其实是这样的函数:

//x == 0 
x.invoke = function (){print(0);} 
//x == 1 
x.invoke = function (){print(1);} 
//x == 2 
x.invoke = function (){print(2);} 
//x == 3 
x.invoke = function (){print(3);}
로그인 후 복사

这样,就可以得到正确的结果了。
实际应用中的例子
好了,理论知识已经够多了,我们下面来看看现实世界中的 JavaScript 函数式编程。有很多人为使 JavaScript 具有面向对象风格而做出了很多努力 (JavaScript 本身具有 可编程性),事实上,面向对象并非必须,使用函数式编程或者两者混合使用可以使代码更加优美,简洁。
jQuery 是一个非常优秀 JavaScript/Ajax 框架,小巧,灵活,具有插件机制,事实上,jQuery 的插件非常丰富,从表达验证,客户端图像处理,UI,动画等等。而 jQuery 最大的特点正如其宣称的那样,改变了人们编写 JavaScript 代码的风格。
优雅的 jQuery
有经验的前端开发工程师会发现,平时做的最多的工作有一定的模式:选择一些 DOM 元素,然后将一些规则作用在这些元素上,比如修改样式表,注册事件处理器等。因此 jQuery 实现了完美的 CSS 选择器,并提供跨浏览器的支持:
清单 15. jQuery 选择器

var cons = $("p.note");// 找出所有具有 note 类的 p 
var con = $("p#con");// 找出 id 为 con 的 p 元素 
var links = $("a");// 找出页面上所有的链接元素
로그인 후 복사

当然,jQuery 的选择器规则非常丰富,这里要说的是:用 jQuery 选择器选择出来的 jQuery 对象本质上是一个 List,正如 LISP 语言那样,所有的函数都是基于 List 的。
有了这个 List,我们可以做这样的动作:
清单 16. jQuery 操作 jQuery 对象 (List)

cons.each( function (index){ 
$( this ).click( function (){ 
//do something with the node 
}); 
});
로그인 후 복사

想当与对 cons 这个 List中的所有元素使用 map( 还记得我们前面提到的 map 吗? ),操作结果仍然为一个 List。我们可以任意的扩大 / 缩小这个列表,比如:
清单 17. 扩大 / 缩小 jQuery 集合

cons.find("span.title");// 在 p.note 中进行更细的筛选 
cons.add("p.warn");// 将 p.note 和 p.warn 合并起来 
cons.slice(0, 5);// 获取 cons 的一个子集
로그인 후 복사

现在我们来看一个小例子,假设有这样一个页面:
清单 18. 页面的 HTML 结构

<p class="note"> 
<span class="title">Hello, world</span> 
</p> 
<p class="note"> 
<span class="title">345</span> 
</p> 
<p class="note"> 
<span class="title">Hello, world</span> 
</p> 
<p class="note"> 
<span class="title">67</span> 
</p> 
<p class="note"> 
<span class="title">483</span> 
</p>
로그인 후 복사

效果如下:
图 1. 过滤之前的效果
我们通过 jQuery 对包装集进行一次过滤,jQuery 的过滤函数可以使得选择出来的列表对象只保留符合条件的,在这个例子中,我们保留这样的 p,当且仅当这个 p 中包含一个类名为 title 的 span,并且这个 span 的内容为数字:
清单 19. 过滤集合

var cons = $("p.note").hide();// 选择 note 类的 p, 并隐藏 
cons.filter( function (){ 
return $( this ).find("span.title").html().match(/^\d+$/); 
}).show();
로그인 후 복사

效果如下图所示:
图 2. 过滤之后的效果
我们再来看看 jQuery 中对数组的操作 ( 本质上来讲,JavaScript 中的数组跟 List 是很类似的 ),比如我们在前面的例子中提到的 map 函数,过滤器等:
清单 20. jQuery 对数组的函数式操作

var mapped = $.map([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 
function (n){ 
return n + 1; 
}); 
var greped = $.grep([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 
function (n){ 
return n % 2 == 0; 
});
로그인 후 복사

mapped 将被赋值为 :
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
而 greped 则为:
[2, 4, 6, 8, 10]
我们再来看一个更接近实际的例子:
清单 21. 一个页面刷新的例子

function update(item){ 
return 
function (text){ 
$("p#"+item).html(text); 
} 
} 
function refresh(url, callback){ 
var params = { 
type : "echo", 
data : "" 
}; 
$.ajax({ 
type:"post", 
url:url, 
cache: false , 
async: true , 
dataType:"json", 
data:params, 
success: function (data, status){ 
callback(data); 
}, 
error: function (err){ 
alert("error : "+err); 
} 
}); 
} 
refresh("action.do/op=1", update("content1")); 
refresh("action.do/op=2", update("content2")); 
refresh("action.do/op=3", update("content3"));
로그인 후 복사

首先声明一个柯里化的函数 update,这个函数会将传入的参数作为选择器的 id,并更新这个 p 的内容 (innerHTML)。然后声明一个函数 refresh,refresh 接受两个参数,第一个参数为服务器端的 url,第二个参数为一个回调函数,当服务器端成功返回时,调用该函数。
然后我们陆续调用三次 refresh,每次的 url 和 id 都不同,这样可以将 content1,content2,conetent3 的内容通过异步方式更新。这种模式在实际的编程中相当有效,因为关于如何与服务器通信,以及如果选取页面内容的部分被很好的抽象成函数,现在我们需要做的就是将 url 和 id 传递给 refresh,即可完成需要的动作。函数式编程在很大程度上降低了这个过程的复杂性,这正是我们选择使用该思想的最终原因。
结束语
实际的应用中,不会囿于函数式或者面向对象,通常是两者混合使用,事实上,很多主流的面向对象语言都在不断的完善自己,比如加入一些函数式编程语言的特征等,JavaScript 中,这两者得到了良好的结合,代码不但可以非常简单,优美,而且更易于调试。
文中仅仅提到 jQuery 特征的一小部分,如果感兴趣,则可以在参考资料中找到更多的链接,jQuery 非常的流行,因此你可以找到很多论述如何使用它的文章。 

원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿