[No0000104]JavaScript-基础课程4
要说 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的更多相关文章
- 妙味课堂——JavaScript基础课程笔记
集中时间把秒微课堂JS的基础课程看完,并且认真完成了课后练习.感觉在JS方面的技能算是入了个门了.课后练习的作业完成的代码我都汇总在了这里.至于视频课的学习笔记,则记录如下. 第01课JS入门基础_热 ...
- JavaScript基础---语言基础(1)
写在前面: 通过四篇博客把JS基础中的基础整理一下,方便自己查阅,这些内容对于实际项目开发中也许并不会在意,但是作为JS的语言基础,自觉还是应该熟悉.在完成这三篇博客(JavaScript基础---语 ...
- JavaScript基础视频教程总结(131-140章)
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- JavaScript基础视频教程总结(121-130章)
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- JavaScript基础视频教程总结(111-120章)
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- JavaScript基础视频教程总结(101-110章)
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- JavaScript基础视频教程总结(091-100章)
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- JavaScript基础视频教程总结(081-090章)
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- JavaScript基础视频教程总结(071-080章)
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- JavaScript基础视频教程总结(061-070章)
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
随机推荐
- Lua:Nginx Lua环境配置,第一个Nginx Lua代码
一.编译安装LuaJIT Lua:编译安装LuaJIT,第一个Lua程序 http://blog.csdn.net/guowenyan001/article/details/48250427 二.下载 ...
- 浏览器URL参数解决方案
function getUrlParams() { var search = window.location.search; // 写入数据字典 , search.length).split(&quo ...
- Mydumper介绍
Mydumper是一个针对MySQL和Drizzle的高性能多线程备份和恢复工具.开发人员主要来自MySQL,Facebook,SkySQL公司.目前已经在一些线上使用了Mydumper. 一.Myd ...
- Linux虚拟文件系统
从文件 I/O 看 Linux 的虚拟文件系统 1 引言 Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等.通过使用同一套文件 I/O 系统 调用即可对 Linux ...
- ql.io来自ebay的api快速集成的构建api的框架
说的有点绕口,实际上是为了减轻在Web上请求数据的复杂度,eBay推出了自己的Web查询语言——ql.io,ql.io将多个独立的API请求绑定成一个单独的请求. ---待续
- linux每日命令(11):cat命令
cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用. 一.命令格式: cat [参数] [文件] ...
- <BEA-141281> <unable to get file lock, will retry ...>
原文:http://gdutlzh.blog.163.com/blog/static/164746951201291903824812/ <BEA-141281> <unable t ...
- 【Unity】UGUI无法修改UI元素的Pivot锚点位置
如下图,要点击切换左边的Toggle按钮变为Pivot才可以编辑Pivot! 参考: https://answers.unity.com/questions/871238/cant-change- ...
- Java知多少(66)输入输出(IO)和流的概述
输入输出(I/O)是指程序与外部设备或其他计算机进行交互的操作.几乎所有的程序都具有输入与输出操作,如从键盘上读取数据,从本地或网络上的文件读取数据或写入数据等.通过输入和输出操作可以从外界接收信息, ...
- 对TextFile格式文件的lzo压缩建立index索引
转自:http://blog.csdn.net/yangbutao/article/details/8519572 hadoop中可以对文件进行压缩,可以采用gzip.lzo.snappy等压缩算法. ...