要说 JavaScript 和其他较为常用的语言最大的不同是什么,那无疑就是 JavaScript 是函数式的语言,函数式语言的特点如下:

函数为第一等的元素,即人们常说的一等公民。就是说,在函数式编程中,函数是不依赖于其他对象而独立存在的(对比与 Java,函数必须依赖对象,方法是对象的方法)。

函数可以保持自己内部的数据,函数的运算对外部无副作用(修改了外部的全局变量的状态等),关于函数可以保持自己内部的数据这一特性,称之为闭包。

由于 JavaScript 支持函数式编程,我们随后会发现 JavaScript 许多优美而强大的能 力,这些能力得力于以下主题:匿名函数,高阶函数,闭包及柯里化等。熟悉命令式语言的开发人员可能对此感到陌生,但是使用 lisp,scheme 等函数式语言的开发人员则觉得非常亲切。

匿名函数在函数式编程语言中,术语成为 lambda 表达式。顾名思义,匿名函数就是没有 名字的函数,这个是与日常开发中使用的语言有很大不同的,比如在 C/Java 中,函数和方法必须有名字才可以被调用。在 JavaScript 中,函数可以没有名字,而且这一个特点有着非凡的意义

通常,以一个或多个函数为参数的函数称之为高阶函数。高阶函数在命令式编程语言中有对应的实现,比如 C 语言中的函数指针,Java 中的匿名类等,但是这些实现相对于命令式编 程语言的其他概念,显得更为复杂。

虽然在 C 语言中可以通过函数指针的方式来实现高阶函数,但是随着高阶函数的“阶” 的增高,指针层次势必要跟着变得很复杂,那样会增加代码的复杂度,而且由于 C 语言是 强类型的,因此在数据类型方面必然有很大的限制。

柯里化就是预先将函数的某些参数传入,得到一个简单的函数,但是预先传入的参数被保存在闭包中,因此会有一些奇特的特性。

var adder = function(num){
return function(y){
return num + y;
}
}
var inc = adder(1);
var dec = adder(-1);
//这里的 inc/dec 两个变量事实上是两个新的函数,可以通过括号来调用,比如下例中的用法:
//inc, dec现在是两个新的函数,作用是将传入的参数值(+/-)1 print(inc(99));//100
print(dec(101));// print(adder(100)(2));//102
print(adder(2)(100));//

根据柯里化的特性,我们可以写出更有意思的代码,比如在前端开发中经常会遇到这样的情况,当请求从服务端返回后,我们需要更新一些特定的页面元素,也就是局部刷新的概 念。使用局部刷新非常简单,但是代码很容易写成一团乱麻。而如果使用柯里化,则可以很大程度上美化我们的代码,使之更容易维护。

//update会返回一个函数,这个函数可以设置id属性为item的web元素的内容
function update(item){
return function(text){
$("di##+item).html(text);
}
} //Ajax请求,当成功是调用参数callback
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?target=news",update("newsPanel"));
refresh("action.do?target=articles",update("articlePanel"));
refresh("action.do?target=pictures",update("picturePanel"));
//其中,update 函数即为柯里化的一个实例,它会返回一个函数,即:
update("newsPanel") = function(text){
$("div#newsPanel").html(text);
}

由于 update(“newsPanel”)的返回值为一个函数,需要的参数为一个字符串,因此在 refresh 的 Ajax 调用中,当 success 时,会给 callback 传入服务器端返回的数据信息,从而实现 newsPanel 面板的刷新,其他的文章面板 articlePanel,图片面板 picturePanel 的刷新均采取这种方式,这样,代码的可读性,可维护性均得到了提高。

通常来讲,函数式编程的谓词(关系运算符,如大于,小于,等于的判断等),以及运算 (如加减乘数等)都会以函数的形式出现

因此,可以首先对这些常见的操作进行一些包装,以便于我们的代码更具有“函数式”风格:

function abs(x){ return x>0?x:-x;}
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; }
function negative(x){ return x<0; }
function positive(x){ return x>0; }
function sin(x){ return Math.sin(x); }
function cos(x){ return Math.cos(x); }

函数式编程的特点当然不在于编码风格的转变,而是由更深层次的意义。

使用 Y-结合子,可以做到对匿名函数使用递归。

var Y = function(f) { return (function(g) {
return g(g); })(function(h) {
return function() {
return f(h(h)).apply(null, arguments);
}; });
};
//
var factorial = Y(function(func){
return function(x){
return x == 0 ? 1 : x * func(x-1);
}
}); factorial(10);
//
Y(function(func){
return function(x){
return x == 0 ? 1 : x * func(x-1);
}
})(10);
//不要被上边提到的 Y-结合子的表达式吓到,事实上,在 JavaScript 中,我们有一种简单的方法来实现 Y-结合子:
var fact = function(x){
return x == 0 : 1 : x * arguments.callee(x-1);
}
fact(10);
//
(function(x){
return x == 0 ? 1 : x * arguments.callee(x-1);
})(10);//

其中,arguments.callee 表示函数自身,而 arguments.caller 表示函数调用者,因此省去了很多复杂的步骤。

//函数的不动点
function fixedPoint(fx, first){
var tolerance = 0.00001;
function closeEnough(x, y){return less( abs( sub(x, y) ), tolerance)};
function Try(guess){//try 是javascript中的关键字,因此这个函数名为大写
var next = fx(guess);
//print(next+" "+guess);
if(closeEnough(guess, next)){
return next;
}else{
return Try(next);
}
};
return Try(first);
} // 数层嵌套函数, function sqrt(x){
return fixedPoint( function(y){
return function(a, b){ return div(add(a, b),2);}(y, div(x, y));
},
1.0);
} print(sqrt(100));

fiexedPoint 求函数的不动点,而 sqrt 计算数值的平方根。

正如第三章提到的,JavaScript 对象是一个属性的集合,另外有一个隐式的对象:原型对象。原型的值可以是一个对象或者 null。一般的引擎实现中,JS 对象会包含若干个隐 藏属性,对象的原型由这些隐藏属性之一引用,我们在本文中讨论时,将假定这个属性的名 称为"__proto__"(事实上,SpiderMonkey 内部正是使用了这个名称,但是规范中并未做要求,因此这个名称依赖于实现)。

由于原型对象本身也是对象,根据上边的定义,它也有自己的原型,而它自己的原型对象又可以有自己的原型,这样就组成了一条链,这个链就是原型链。

JavaScritp 引擎在访问对象的属性时,如果在对象本身中没有找到,则会去原型链中查找,如果找到,直接返回值,如果整个链都遍历且没有找到属性,则返回 undefined.原 型链一般实现为一个链表,这样就可以按照一定的顺序来查找。

var base = {
name : "base",
getInfo : function(){
return this.name;
}
}
var ext1 = {
id : 0,
__proto__ : base
} var ext2 = {
id : 9,
__proto__ : base
} print(ext1.id);
print(ext1.getInfo());
print(ext2.id);
print(ext2.getInfo());
0
base
9
base

var base = {
name : "base",
getInfo : function(){
return this.name;
}
}
var ext1 = {
id : 0,
name : "ext1",
__proto__ : base
} print(ext1.id);
print(ext1.getInfo());
//
0
ext1

这个运行效果同样验证了原型链的运行机制:从对象本身出发,沿着__proto__查找, 直到找到属性名称相同的值(没有找到,则返回 undefined)。

var base = {
name : "base",
getInfo : function(){
return this.id + ":" + this.name;
}
} var ext1 = {
id : 0,
__proto__ : base
} print(ext1.getInfo());

我们在 getInfo 函数中加入 this.id,这个 id 在 base 对象中没有定义。同时,删掉了 ext1 对象中的 name 属性,执行结果如下:

0:base

应该注意的是,getInfo 函数中的 this 表示原始的对象,而并非原型对象。上例中的 id 属性来自于 ext1 对象,而 name 来自于 base 对象。如果对象没有显式的声明自己的”__proto__”属性,这个值默认的设置为Object.prototype,而 Object.prototype 的”__proto__”属性的值为”null”,标志着原型链的终结。

我们在来讨论一下构造器,除了上边提到的直接操作对象的__proto__属性的指向以外,JavaScript 还支持构造器形式的对象创建。构造器会自动的为新创建的对象设置原型 对象,此时的原型对象通过构造器的 prototype 属性来引用。

我们以例子来说明,将 Task 函数作为构造器,然后创建两个实例 task1, task2:

function Task(id){
this.id = id;
} Task.prototype.status = "STOPPED";
Task.prototype.execute = function(args){
return "execute task_"+this.id+"["+this.status+"]:"+args; } var task1 = new Task(1);
var task2 = new Task(2); task1.status = "ACTIVE";
task2.status = "STARTING"; print(task1.execute("task1"));
print(task2.execute("task2"));
//execute task_1[ACTIVE]:task1
execute task_2[STARTING]:task2

构造器会自动为 task1,task2 两个对象设置原型对象 Task.prototype,这个对象被 Task(在此最为构造器)的 prototype 属性引用,参看下图中的箭头指向。

由于 Task 本身仍旧是函数,因此其”__proto__”属性为 Function.prototype, 而内 建的函数原型对象的”__proto__”属性则为Object.prototype 对象。最后 Obejct.prototype 的”__proto__”值为 null.

最新文章

  1. Java第八周学习总结
  2. HttpURLConnection网络请求
  3. linq 延迟执行带来的困扰
  4. 数据文件 和日志文件 收缩 Sql Server
  5. Delphi推出Delphi XE4支持IOS开发
  6. secureCRT常用设置
  7. 基于bootstrap的轮播广告页,带图片和文字
  8. Web API 2:Action的返回类型
  9. POJ 2560 Freckles Prime问题解决算法
  10. 【2017-05-04】winfrom进程、线程
  11. 关于python 2.7要求输出汉字问题
  12. Python构建发布
  13. 微信小程序提交审核并发布详细流程
  14. zepto中的属性设置
  15. Qt使用正则表达式去掉小数位多余的0
  16. 在Qt项目中如何添加一个已有的项目作为子项目
  17. InstallShield打包,以及集成TFS、JenKins
  18. java 异常和异常处理Exception
  19. 用ndp部署storm应用
  20. 【javascript】判断是否微信浏览器的最佳实践

热门文章

  1. 使用 bibtex4word 实现在 office word 中管理并插入参考文献
  2. Atitit.mysql 5.0 5.5 &#160;5.6 5.7 &#160;新特性 新功能
  3. 100种不同图片切换效果插件pageSwitch
  4. 【WPF】自定义形状的按钮Button
  5. OpenGL教程一
  6. java 汉诺塔实现自动演示
  7. Hadoop、Spark 集群环境搭建
  8. volatile内存语义
  9. [Algorithm] Warm-up puzzles
  10. 14桥接模式Bridge