[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));//100

print(adder(100)(2));//102 
print(adder(2)(100));//102

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

//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);//3628800

其中,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.

原文地址:https://www.cnblogs.com/Chary/p/No0000104.html