您的位置:首页 > Web前端 > JavaScript

JavaScript 函数式编程理解笔记 (2)

2017-04-11 20:00 711 查看
本笔记经过学习

https://www.ibm.com/developerworks/cn/web/1006_qiujt_jsfunctional/ 后书写,很大部分都相同,但一字一句包括代码都是自己书写。

JavaScript 函数式编程理解笔记(2)

JavaScript中的函数式编程

函数式编程风格

在JavaScript中,函数本身做为一种特殊对象,属于顶层对象,不依赖于其他对象存在。

[b]清单1.JavaScript中的求和[/b]

function sum(){
var res = 0;
for (var i = 0; i < arguments.length; i++){
res += parseInt(argument[i]);
}
return res;
}
print(sum(1,2,3));
print(sum(1,2,3,4,5,6));


运行此段代码,结果如下:

6
31


如果要完全模拟函数式编码的风格,我们可以定义一些诸如以下的小函数以及谓词:

[b]清单2.一些简单的函数抽象[/b]

function add(a, b){  return a+b; }
function sub(a, b){  return a-b; }
function mul(a, b){  return a*b; }
function div(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; }


[b]清单3.函数式编程风格[/b]

//修改之前的代码
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函数,则形成了闭包。

[b]清单4.一个闭包的例子[/b]

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的调用而改变,是因为o1和o2是不同的实例。

[b]清单5.jQuery中的闭包[/b]

var con = $("div#con");
setTimeout(function(){
con.css({background:"gray"});
},2000);


上面的代码使用了jQuery的选择器,找到id为con的div元素,注册计时器,两秒后将此元素的背景设置为灰色。神奇之处在于,在调用了setTimeout函数之后,con依旧被保留在了函数中,两秒之后,这个元素的背景色的确改变了。应注意的是,setTimeout在调用之后已经返回了,但con没有被释放,因为con引用了全局作用域里的变量con。

由于闭包的特殊性,要小心使用闭包,来看一个容易令人困惑的例子:

[b]清单6.错误的使用闭包[/b]

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);
}
}


[b]清单7.错误的结果[/b]

clouseText();     //调用这个函数,向outter数组中添加对象
for (var i = 0; i < outter.length; i++){
outter[i].invoke();
}


出乎意料的是,将打印:

4
4
4
4


而不是1,2,3,4这样的序列。每一个x都有自己的no,text,invoke字段,但是invoke却打印了最后一个i的值。错误的原因在于注册的函数:

[b]清单8.错误的原因[/b]

function invoke(){
print(i);
}


每一个invoke都是如此,只有当outter[i].invoke被调用的时候,i的值才会被取到,而i由于是局部变量,for循环退出时i的值总是为4,所以每次打印的结果都会是4。因此,需要对这个函数进行更改:

[b]清单9.正确的用法[/b]

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);
}
}


通过将函数柯里化,可以达到我们预期的效果,即打印出1,2,3,4这样的序列。

实际应用中的例子

[b]优雅的jQuery[/b]

jQuery实现了完美的CSS选择器,并提供跨浏览器的支持:

[b]清单10.jQuery选择器[/b]

var cons = $("div.note");// 找出所有具有 note 类的 div
var con = $("div#con");  // 找出 id 为 con 的 div 元素
var links = $("a");      // 找出页面上所有的链接元素


jQuery的选择器规则很丰富。这里需要注意的是,jQuery选择出来的jQuery对象本质上是一个List。

有了List,我们可以这么操作:

[b]清单11.jQuery操作jQuery对象(List)[/b]

cons.each( function (index){
$( this ).click( function (){
//do something with the node
});
});


同时,我们也可以扩大或缩小这个List:

[b]清单12.扩大/缩小jQuery集合[/b]

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


现在有一个小例子,假设有这样一个页面:

[b]清单13.页面的HTML结构[/b]

<div class="note">
<span class="title">Hello, world</span>
</div>
<div class="note">
<span class="title">345</span>
</div>
<div class="note">
<span class="title">Hello, world</span>
</div>
<div class="note">
<span class="title">67</span>
</div>
<div class="note">
<span class="title">483</span>
</div>


效果图如下:

[b]图1.过滤之前的效果[/b]



我们通过jQuery对这个集合做一个过滤,在这个例子中,我们保留这样的div,当且仅当这个div中包含一个类名为title的span,而且span中的内容是数字:

[b]清单14.过滤集合[/b]

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


效果图如下:

[b]图2.过滤之后的效果[/b]



再来看看jQuery中对数组的操作:

[b]清单15.jQuery对数组的函数式操作[/b]

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]。

最后看一个更接近实际的例子:

[b]清单16.一个页面刷新的例子[/b]

function update (item){
return function (text){
$("div#"+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);
}
});
}


首先声明一个柯里化的函数 update,这个函数会将传入的参数作为选择器的 id,并更新这个 div 的内容 (innerHTML)。然后声明一个函数 refresh,refresh 接受两个参数,第一个参数为服务器端的 url,第二个参数为一个回调函数,当服务器端成功返回时,调用该函数。

这种模式在实际的编程中相当有效,因为关于如何与服务器通信,以及如果选取页面内容的部分被很好的抽象成函数,现在我们需要做的就是将 url 和 id 传递给 refresh,即可完成需要的动作。函数式编程在很大程度上降低了这个过程的复杂性,这正是我们选择使用该思想的最终原因。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息