要说 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.

[No0000104]JavaScript-基础课程4的更多相关文章

  1. 妙味课堂——JavaScript基础课程笔记

    集中时间把秒微课堂JS的基础课程看完,并且认真完成了课后练习.感觉在JS方面的技能算是入了个门了.课后练习的作业完成的代码我都汇总在了这里.至于视频课的学习笔记,则记录如下. 第01课JS入门基础_热 ...

  2. JavaScript基础---语言基础(1)

    写在前面: 通过四篇博客把JS基础中的基础整理一下,方便自己查阅,这些内容对于实际项目开发中也许并不会在意,但是作为JS的语言基础,自觉还是应该熟悉.在完成这三篇博客(JavaScript基础---语 ...

  3. JavaScript基础视频教程总结(131-140章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  4. JavaScript基础视频教程总结(121-130章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  5. JavaScript基础视频教程总结(111-120章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  6. JavaScript基础视频教程总结(101-110章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  7. JavaScript基础视频教程总结(091-100章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. JavaScript基础视频教程总结(081-090章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  9. JavaScript基础视频教程总结(071-080章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  10. JavaScript基础视频教程总结(061-070章)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

随机推荐

  1. java中使用队列:java.util.Queue(转)

    队列是一种特殊的线性表,是运算受到限制的一种线性表,只允许在表的一端进行插入,而在另一端进行删除元素的线性表.队尾(rear)是允许插入的一端.队头(front)是允许删除的一端.空队列是不含元素的空 ...

  2. phpmyadmin登录提示mysqli_real_connect(): (HY000/2002): No such file or directory和mysql8登录失败的问题

    网上的解决方法有很多,但都无法解决我的问题,最后在stackoverflow上找到解决方法,原文地址:https://stackoverflow.com/questions/41881123/mysq ...

  3. 对ThreadLocal实现原理的一点思考

    前言 在<透彻理解Spring事务设计思想之手写实现>中,已经向大家揭示了Spring就是利用ThreadLocal来实现一个线程中的Connection是同一个,从而保证了事务.本篇博客 ...

  4. 基于jQuery鼠标滚轮滑动到页面节点部分

    基于jQuery鼠标滚轮滑动到页面节点部分.这是一款基于jQuery+CSS3实现的使用鼠标滚轮或者手势滑动到页面节点部分特效.效果图如下: 在线预览   源码下载 实现的代码. html代码: &l ...

  5. oracle删除数据库中的所有表

    连接:http://linben.blog.51cto.com/6205951/1293619 1.先禁用数据库中所有的约束 select 'alter table ' || table_name | ...

  6. CentOS系统实现SSH无密码登录的方法

    一.环境配置 1.服务端:CentOS release 5.3 IP:222.73.115.198 2.客服端:CentOS release 5.8 IP:192.168.4.244 二.配置SSH无 ...

  7. 大数据基础篇----jvm的知识点归纳-5个区和垃圾回收机制

    一直对jvm看了又忘,忘了又看的.今天做一个笔记整理存放在这里. 我们先看一下JVM的内存模型图: 上面有5个区,这5个区干嘛用的呢? 我们想象一个场景: 我们有一个class文件,里面有很多的类的定 ...

  8. Springframework和Hibernate版本对应关系

    org.springframework 3.0.x对应org.hibernate4.0.x版本 org.springframework 3.2.x对应org.hibernate4.2.x版本 org. ...

  9. Golang中下划线的使用

    https://studygolang.com/articles/17479?fr=sidebar

  10. [Tensorflow] Object Detection API - predict through your exclusive model

    开始预测 一.训练结果 From: Testing Custom Object Detector - TensorFlow Object Detection API Tutorial p.6 训练结果 ...