zoukankan      html  css  js  c++  java
  • [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.

  • 相关阅读:
    springAOP源码分析之篇三:代理对象的执行
    springAOP源码分析之篇二:代理对象的生成
    css样式float造成的浮动“塌陷”问题的解决办法
    css font的简写规则
    CSS学习
    工具
    用过的库
    The requested URL ***** was not found on this serve
    修改DeDe标签Pagelist分页样式
    DedeCms autoindex和itemindex使用介绍
  • 原文地址:https://www.cnblogs.com/Chary/p/No0000104.html
Copyright © 2011-2022 走看看