本笔记经过学习
https://www.ibm.com/developerworks/cn/web/1006_qiujt_jsfunctional/ 后书写,很大部分都相同,但一字一句包括代码都是自己书写。
JavaScript 函数式编程理解笔记(2)
JavaScript中的函数式编程
函数式编程风格
在JavaScript中,函数本身做为一种特殊对象,属于顶层对象,不依赖于其他对象存在。
清单1.JavaScript中的求和
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
如果要完全模拟函数式编码的风格,我们可以定义一些诸如以下的小函数以及谓词:
清单2.一些简单的函数抽象
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; }
清单3.函数式编程风格
//修改之前的代码
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函数,则形成了闭包。
清单4.一个闭包的例子
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是不同的实例。
清单5.jQuery中的闭包
var con = $("div#con");
setTimeout(function(){
con.css({background:"gray"});
},2000);
上面的代码使用了jQuery的选择器,找到id为con的div元素,注册计时器,两秒后将此元素的背景设置为灰色。神奇之处在于,在调用了setTimeout函数之后,con依旧被保留在了函数中,两秒之后,这个元素的背景色的确改变了。应注意的是,setTimeout在调用之后已经返回了,但con没有被释放,因为con引用了全局作用域里的变量con。
由于闭包的特殊性,要小心使用闭包,来看一个容易令人困惑的例子:
清单6.错误的使用闭包
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);
}
}
清单7.错误的结果
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的值。错误的原因在于注册的函数:
清单8.错误的原因
function invoke(){
print(i);
}
每一个invoke都是如此,只有当outter[i].invoke被调用的时候,i的值才会被取到,而i由于是局部变量,for循环退出时i的值总是为4,所以每次打印的结果都会是4。因此,需要对这个函数进行更改:
清单9.正确的用法
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这样的序列。
实际应用中的例子
优雅的jQuery
jQuery实现了完美的CSS选择器,并提供跨浏览器的支持:
清单10.jQuery选择器
var cons = $("div.note");// 找出所有具有 note 类的 div
var con = $("div#con"); // 找出 id 为 con 的 div 元素
var links = $("a"); // 找出页面上所有的链接元素
jQuery的选择器规则很丰富。这里需要注意的是,jQuery选择出来的jQuery对象本质上是一个List。
有了List,我们可以这么操作:
清单11.jQuery操作jQuery对象(List)
cons.each( function (index){
$( this ).click( function (){
//do something with the node
});
});
同时,我们也可以扩大或缩小这个List:
清单12.扩大/缩小jQuery集合
cons.find("span.title");// 在 div.note 中进行更细的筛选
cons.add("div.warn"); // 将 div.note 和 div.warn 合并起来
cons.slice(0, 5); // 获取 cons 的一个子集
现在有一个小例子,假设有这样一个页面:
清单13.页面的HTML结构
<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>
效果图如下:
图1.过滤之前的效果
我们通过jQuery对这个集合做一个过滤,在这个例子中,我们保留这样的div,当且仅当这个div中包含一个类名为title的span,而且span中的内容是数字:
清单14.过滤集合
var cons = $("div.note").hide(); //选择note类的div,并隐藏
cons.filter(function (){
return $(this).find("span.title").html().match(/^\d+$/);
}).show();
效果图如下:
图2.过滤之后的效果
再来看看jQuery中对数组的操作:
清单15.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]。
最后看一个更接近实际的例子:
清单16.一个页面刷新的例子
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,即可完成需要的动作。函数式编程在很大程度上降低了这个过程的复杂性,这正是我们选择使用该思想的最终原因。