【书 JS语言精粹】第4章 函数

所有的过失在未犯以前,都已定下应处的惩罚。     莎士比亚

所有的选择在未抉择以前,都已定下他未来的筹码。      该佚名

js设计最出色的就是函数设计。

函数用于指定对象的行为。

一般来说,所谓编程。。。就是将一组需求   分解为 ① 一组函数 ② 数据结构

  1. 函数对象(function objects)(对象------属性名/属性值得集合,且拥有连接到原型对象的隐藏链接。)

    · 对象字面量 连接到  Object.prototype

    · 函数字面量 连接到  Function.prototype (每个函数在创建的时候,都会附加两个隐藏属性:函数的上下文和实现函数行为的代码)(因为函数是对象,所以可以有方法,所以可以被保存在变量里,对象数组中,可以被当做参数传递给其他函数,可以被当做返回值被return)(与对象唯一不同的是,函数可以被调用)

  2. 函数字面量(function literral)
    函数的字面量一共包含四个部分
    
    1 保留字 function
    
    2 函数名 ,但是也可以被省略。        额。。。就成了匿名函数麽 (anonymous)
      可以通过函数名来调用自己。         额。。。就成了递归函数麽
      函数名特能被调试器和开发工具用来识别函数。
    
    3 ()形参,区切,不会被初始化成undefined,是调用该函数的时候传进来的实参的值。
    
    4 {}函数体。
    
    函数字面量可以出现在任何表达式可以出现的地方。
    可以被定义在其他函数中。
    一个内部的函数除了可以访问自己的参数和变量,还可以自由的访问嵌套他的父级函数的参数和变量。
    
    创建函数对象的时候,会包含着一个链接上下文的链接,这被称之为闭包。(closure)
  3. 调用(invocation)
    
    主函数  =》  子函数  
    ① 暂停主函数的执行
    ② 主函数把控制权和参数传递给子函数 (关于参数,除了传递过去的形参,还有两个附加参数:this和arguments)
    
    【重头戏啊】
    函数中的,其实this才重要。
    果不其然,无论到哪里,想清楚自己才是最最最重要。
    
    js一共有四种调用方式
      · 方法调用模式
      · 函数调用模式
      · 构造函数调用模式
      · apply 调用模式
    
    以上四种方式,在如何初始化this方面存在差异。
    (反正挺老师说过,箭头函数和普通函数中的this是不一样的。)
    
    
    调用运算符 ()
    (逗号分隔)
    调用时候的实体参数,可以和函数定义的形式参数的个数不匹配。
    实体参数 》 形参                    多出来的参数将会被忽略
    反之                               没有被赋值得到形参,讲会是undefined。
    另                                 不会对数据类型进行检查。。。。。。
    
    以下 4 到 7 其实是调用的子集~~~ 
  4. 方法调用模式(the method invocation pattern)
    // 创建 myObject 对象。
    // 该对象有一个 value 属性 和 一个increment 方法
    // increment 方法接受一个可选参数
    // 如果调用函数的时候,实体参数不是数字的场合
    // 那么会有一个默认值 数字1
    
    // 对象体定义
    var myObject = {
        value : 0 ,
        increment : function (inc) {
    
            this.value += typeof inc === 'number' ? inc : 1; 
        }
    
    }
    
    // 方法调用模式
    // 当实体参数的个数少于形参的时候
    // 形参将会为undefined
    myObject.increment();
    document.writeln(myObject.value); // 不是数字的场合,返回1 
    
    
    // 实体参数是数字的场合
    myObject.increment(2);
    document.writeln(myObject.value); // 是数字的场合,
                                      // 返回 之前的值(突然发现上面返回1以后value就是1 了,
                                      // 然后+=2 ,于是现在返回的是3)
  5. 函数调用模式(the function invocation pattern)
    // 对象体定义
    var myObject = {
        value : 0 ,
        increment : function (inc) {
    
            this.value += typeof inc === 'number' ? inc : 1; 
        }
    }
    // 当实体参数的个数少于形参的时候
    // 形参将会为undefined
    myObject.increment();
    // 实体参数是数字的场合
    myObject.increment(2);
    ///////////////////////////////上集回顾,为了整体可以运行哈/////////////////////
    
    
    // var sum = add();
    // 创建一个名为add的函数变量
    var add = function(a,b){
        return a + b;
    }
    
    
    // 当给一个对象赋值一个他没有的属性的时候
    // 该对象会被扩展
    // eg 给myObject 增加一个double 方法
    
    myObject.double = function(){
        
        var that = this;
        
        var helper = function (){
            
            // 以函数调用模式调用了helper
            that.value = add(that.value , that.value);  
        }
    
        helper();
    }
    
    // 方法调用模式 调用了double
    myObject.double();
    document.writeln(myObject.value); 
    
    // 终于对了 上文提要 一句都不能少 !!! 汗-_-|| 
    
  6. 构造函数调用模式(the constructor invocation pattern)
    // 创建一个 Quo 的构造器函数 它构造一个带有 status 属性的对象
    // 一个函数如果创建的目的,就是为了结合new关键字来调用,那么他就是构造函数
    // 但作者不推荐使用
    var Quo = function (string){
        this.status = string;
    }
    
    // 给Quo所有实例提供一个名为 get_status的公共方法。
    Quo.prototype.get_status = function(){
        return this.status;
    }
    
    var myQuo = new Quo("confused");
    document.writeln(myQuo.get_status());
    
  7. Apply调用模式(the apply invocation pattern)
    // simple case
    // 创建一个名为add的函数变量
    var add = function(a,b){
        return a + b;
    }
    
    var array = [3,4];
    var sum = add.apply(null,array);
    console.log(sum);
    var Quo = function (string){
        this.status = string;
    }
    
    Quo.prototype.get_status = function(){
        return this.status;
    }
    
    // statusObject并没有继承自Quo.prototype
    // 但我们可以在statusObject上,
    // 通过apply 这种方式 来调用get_status方法
    // 尽管statusObject其实并没有一个名为get_status的方法
    var statusObject = {
        status : "A-OK"
    }
    
    var status = Quo.prototype.get_status.apply(statusObject);
    
    console.log(status);
  8. 参数(arguments)
    // 构造一个好多数相加的函数
    // 注意: 该函数内部定义的sum不会影响到外部定义的sum
    // 该函数只能看到内部定义的那个sum
    
    var sum = function(){
    
        var i,sum = 0;
        for(i=0;i<arguments.length;i++){
            sum += arguments[i];
        }
        return sum;
    };
    
    console.log(sum(1,2,3,4,5,6,7,8,9,10));// (1+10)*10/2=55
  9. 返回(return)
    子函数 () {
    
        return ; // 使子函数提前啊结束,不在执行return以下的代码程序。
    
    } // 结束函数体,讲控制权交还给主函数
    
    主函数 () {
    
        调用 子函数();
    }
    
    // 一个函数总会有一个返回值的,如果都没有的时候,就会返回一个undefined
    
    // 而当是构造函数,且返回对象不是一个对象的时候,则返回this(该新对象)
    
    // 另需要注意的是
    // 当返回值 比如是个json数据类型的时候 
    rerun {
        status:true;
    };
    // 如果写成了下面这样,就不会返回json对象,只会返回undefined
    // 原因就是 js 会在return 后面自动补上 ; 
    // 这是js的一个坑 需要注意
    rerun 
    {
        status:true;
    };
  10. 异常(exceptions)
    var add = function (a,b){
        if(typeof a !== 'number' || typeof b !== 'number' ){
    
            throw {
                
                name : 'TypeError',
                message : 'add needs numbers'
            };
        }
        
        return a + b;
    }
    
    var try_it = function(){
    
        try{
    
            add("seven");
        }catch(e){
    
            document.writeln(e.name + ": " + e.message);
        }
    }
    
    try_it();

  11. 扩充类型的功能(augmenting types)
    // TODO 说是以前是 Object.prototype 需要在这个原型链上面 才可以给对象 追加方法
    // 这个还是没太明白 还需要想想
    
    // 通过给 Function.prototype 增加方法来使得该方法对所有函数都管用
    // 通过给 Function.prototype 增加一个method的方法,下次给对象增加方法的时候就不用必须键入 
    // prototype这几个字符 
    
     Function.prototype.method = function (name,func){
         
         // 因为是通过基本条件增加的方法,所以加一个条件判断
         // 在确定没有该方法的时候,才去追加该方法
         if(!this.prototype[name]){
             
             this.prototype[name] = func;
         }
    
         return this;
     } 
    
    // js没有专门的整数类型 取整???
    // js本身的取整 有些丑陋 
    // 一般会通过给 Number.prototype增加一个 integer 方法来改善它
    // 根据数字的正负 来判定是使用 ceiling 还是 使用 floor
    // 如下
    Number.method('integer',function(){
        return Math[this < 0 ? 'cell' : 'floor'](this);
    });
    
    document.writeln((-10/3).integer());
    document.writeln((10/3).integer());
    
    // js缺少一个trim的方法
    String.method('trim',function(){
        return this.replace(/^\s+|\s$/g,'');
    });
    
    document.writeln('"' + "    neat    ".trim() + '"');
    
  12. 递归(recursion)
    // 1.汉诺塔
    
    var hanoi = function (disc, one , two , three){
    
        if(disc > 0){
            
            hanoi(disc - 1,one , three , two);
            document.writeln('Move disc ' + disc + 'from ' + one + ' to ' + three + '<br>');
            hanoi(disc - 1,two , one , three );
        }
    };
    
    hanoi( 3 , '①' , '②', '③' );
    
    
    // 对 非常 nice 
    // TODO 我还要做一个 图形界面的 回头单独做一个 link 文章
    // 因为觉得 可能需要写一些 css js目测 应该并不会太多
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <script type="text/javascript">
           
            /// 另外一个问题 我在想另外一个问题 递归是不是 和分而治之 的 那个动态规划
            // 就是那个什么把大问题 切成 小问题 分别解决的那个事情 是一起的 
    
            // 【划重点啊】递归函数 
            // 可以非常高效的去遍历树形结构的数据类型
            // 比如浏览器短的文档对象模型DOM
            // 思路就是每次递归调用的时候,都处理指定的树的一小段
    
            // 定义 wait_the_DOM 函数,它从某个指定的节点开始,按HTML源码中的顺序
            // 访问每一个节点
            // 她会调用一个函数
            // 并且依次传递每个节点给他
            // wait_the_DOM 调用自身去处理每一个节点
    
            var wait_the_DOM = function walk(node,func){
            
            // console.log(node);
            // console.log(node.firstChild);
            func(node);
            node = node.firstChild;
            while(node){
                walk(node,func);
                node = node.nextSilbling;
            }
            } 
    
            // 定义 getElementsByAttribute 函数。
            // 它以一个属性名称字符串和一个可选的匹配值作为参数。
            // 她调用 wait_the_DOM ,传递一个用来查找节点属性名的函数作为参数。
            // 匹配的节点会累加到一个结果数组中。
    
            var getElementsByAttribute = function (att , value) {
                var results = [];
    
                wait_the_DOM(document.body, function (node) {
    
                    // console.log(node);
                    var actual = node.nodeType === 1 && node.getAttribute(att);
                    if(typeof actual === 'string' && (actual === value || typeof value !== 'string')){
                        results.push(node);
                    }
                });
                return results;
            };
    
        </script>
    </head>
    <body class='testdiv'>
       <div class='testdiv'>
           1<div>11<div class='testdiv'>111</div></div>
       </div>
       <div>2</div>
       <div class='testdiv'>3</div>
       <script type="text/javascript">
            // for(var ele in getElementsByAttribute('class','testdiv')){
            //     console.log(ele);
            // }
            console.log(getElementsByAttribute('class','testdiv'));
       </script>
    </body>
    </html>
    // 尾递归优化
    // 如果一个函数返回自身递归调用的结果,那么调用过程会被替代为一个循环,可以显著提高速度。
    // js当前没有提供尾递归优化。
    // 深度递归的函数可能会因为 堆栈溢出 而运行失败
    
    // 构建一个带尾递归的函数
    // 因为它会返回自身调用结果,所以是尾递归
    
    // 阶乘 n!=1×2×3×...×(n-1)×n
    var factorial = function factorial (i,a){
    
        a = a || 1;
        if(i<2){
            return a;
        }
        return factorial (i-1,a*i);
    }
    
    console.log(factorial(5));
  13. 作用域(scope)

    // js 迷之作用域
    
    // 。。。。。。
    
    var foo = function (){
        
        var a=3,b=5;
    
        var bar = function () {
            
            var b = 7,c=11; // 此时 a = 3 , b = 7 ,c = 11
                            // 而且 我理解 bar 中的 b 也不是 foo 中的b 
                            // b被盖掉了
            
            console.log("1====="," a= " + a," b= " + b," c= " + c);
    
            a += b+c; //  此时 a = 21 , b = 7 ,c = 11       
    
            console.log("2====="," a= " + a," b= " + b," c= " + c);
    
        }; // 此时 a = 3,b=5 ,c 没有定义
    
        console.log("3====="," a= " + a," b= " + b);    
    
        bar();
        
        // 此时 a = 21,b=5
        console.log("4====="," a= " + a," b= " + b);
    };
    
    foo();
  14. 闭包(closure)
    // 作用域的好处,便是内部函数可以访问 外部函数的参数和变量 
    // 除了 this 和 arguments 
    
    // TODO 一个更有趣的 内部函数 拥有比外部函数 更长的生命周期 ???
    
    var myObject = (function(){
    
        var value = 0;
        
        return {
        
            increment:function(inc){
                value += typeof inc === 'number' ? inc:1;
            },
            getValue:function(){ return value}
        };
    
    }());
    
    console.log(myObject.getValue());
    myObject.increment(5);
    console.log(myObject.getValue());
    
    // 结果是 0 5 
    // 效果是 函数外面 看不到 value 不能直接访问value
    // 当时 通过 return 出来的函数 
    // 可以修改 value的 value  !!!
    // 感觉 有点像 java 里面 封装了那个私有的成员变量
    // 当时可以 set get等等 公开化的成员函数 
    // 去对数据进行 修改操作的调调  ~~~ 
    // 我要找回自己,找回我希冀中的自己
    // 如果曾经错过,我希望未来不要继续迷失
    // 我要努力遇见
    // 遇见那个未来的
    // 喜欢的自己
    // 加油~~~
    // 创建一个quo构造函数
    // 她构造出带有 get_status 方法 和status 私有属性的一个对象。
    
    var quo = function (status){
    
        return {
            
            get_status : function (){
                return status;
            }
        };
    
    };
    
    // 构造一个 quo实例
    var myQuo = quo("chinsei");
    
    console.log(myQuo.get_status());
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
    
       <script type="text/javascript">
            // 定义一个函数 DOM节点 渐渐变色
    
            var fade = function(node){
    
                var level = 1;
                var step = function(){
    
                    var hex = level.toString(16);
                    node.style.backgroundColor = '#FFFF' + hex +hex;
                    if(level < 15){
                        level +=1;
                        setTimeout(step,100);
                        // console.log(level);
                    }else if(level > 0){
    
                        // level -=1;
                        level = 1;
                        // console.log('AA'+level);
                        setTimeout(step,100);
                    }
                };
                setTimeout(step,100);
    
            }
    
            fade(document.body);
       </script>
    </body>
    </html>
    // 这个 李游 老师讲过
    
    // bug 就是 只能alert 出来 3
    // 打印出来的不对 
    
    // 构造一个函数 ,用错误的方式给一个数组中的节点 设置事件处理程序
    // 当点击一个节点时,按照预期,应该 弹出来 123
    // 但是实际上弹出来的 只是 333
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            .testdiv{
                width: 100px;
                height: 100px;
                background-color: orange;
                margin: 10px;
            }
        </style>
    </head>
    <body>
        <div class='testdiv'>1</div>
        <div class='testdiv'>2</div>
        <div class='testdiv'>3</div>
       <script type="text/javascript">
    
            var add_the_handlers = function(nodes){
    
                var i;
                for(i=0;i<nodes.length;i++){
                    
                    nodes[i].onclick = function(e){
                        alert(i);
                    }
                }
            }
    
            add_the_handlers(document.getElementsByClassName('testdiv'));
    
       </script>
    </body>
    </html>
    // 改良以后
    
    // 用正确的方式 绑定点击事件 
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            .testdiv{
                width: 100px;
                height: 100px;
                background-color: orange;
                margin: 10px;
            }
        </style>
    </head>
    <body>
        <div class='testdiv'>1</div>
        <div class='testdiv'>2</div>
        <div class='testdiv'>3</div>
       <script type="text/javascript">
    
            var add_the_handlers = function(nodes){
    
                var helper = function(i){
                    return function(e){
                        alert((i+1));
                    };
                }
    
                var i;
                for(i=0;i<nodes.length;i++){
                    
                    // nodes[i].onclick = function(e){
                    //     alert(i);
                    // }
                    nodes[i].onclick = helper(i);
                }
            }
    
            add_the_handlers(document.getElementsByClassName('testdiv'));
    
       </script>
    </body>
    </html>
  15. 回调(callbacks)
    // 修改前
    
    request = prepare_the_request();
    response = send_request_synchronously(request);
    display(response);
    
    
    
    
    ==========>>>>>>>>>>>>>>>>>
    
    
    
    
    // 修改后
    
    request = prepare_the_request();
    send_request_synchronously(request,response,function(){
        display(response);
    });
    
    // 且 并不是一个线程 数据返回以前就死等 的效果
    // 异步等待数据的获得
    // 达到 一定 收到数据以后才绑定 返回 响应的效果
    
  16. 模块(module)
    // 用来产生 序列号 的对象
    
    // 返回一个用来产生唯一字符串的对象
    // 唯一字符串的组成:前缀 + 序列号
    // 该对象包含一个设置前缀的方法,一个设置序列号的方法
    // 和一个产生唯一字符串的 gensym 方法
    var serial_maker = function(){
    
        var prefix = '';
        var seq = 0;
        return {
    
            set_prefix : function(p){
                prefix = String(p);
            },
            set_seq : function(s){
                seq = s;
            },
            gensym : function(){
                var rs = prefix + seq;
                seq++;
                return rs;
            }
        };
    };
    
    var seqer = serial_maker();
    console.log(seqer);
    seqer.set_prefix('Q');
    seqer.set_seq(1000);
    var unique = seqer.gensym();
    console.log(unique);
     Function.prototype.method = function (name,func){
         
         if(!this.prototype[name]){
             
             this.prototype[name] = func;
         }
    
         return this;
     } 
    
    
    String.method('deentityify',function(){
    
        // 字符实体表。她映射字符实体的名字到对应的字符。
        var eentity = {
    
            quot:'"',
            lt:'<',
            gt:'>'
        };
    
        // 返回deentityify方法
        return function(){
    
            // 这是deentityify方法,调用replace 
            // 查找 ^'&'  $';' 字符串
            // 当这样的字符串被找到 就被 替换成 映射表中的值
            return this.replace(/&([^&;]+);/g,function(a,b){
                var r = eentity[b];
                return typeof r === 'string' ? r : a;
            });
        };
    
    }());// 最后的这个()运算法 立刻调用我们刚刚构造出来的函数。
    // 这个调用所创建并返回的函数才是deentityify方法。
    
    console.log("&lt;&quot;&gt;".deentityify());
  17. 级联(cascade)
    有一些方法没有 返回值 
    不想返回 undefined 想返回 this的时候 ,需要用到级联!!!???
  18. 柯里化(curry)

    柯里化

     Function.prototype.method = function (name,func){
         
         if(!this.prototype[name]){
             
             this.prototype[name] = func;
         }
    
         return this;
     } 
    
    var add = function(a,b){
        return a+b;
    }
    
    Function.method('curry',function(){
    
        var slice = Array.prototype.slice,
            args = slice.apply(arguments),
            that = this;
        
        return function(){
        
            return that.apply(null,args.concat(slice.apply(arguments)));
        };
    });
    
    var add1 = add.curry(1);
    document.writeln(add1(6));
  19. 记忆(memoization)
    // 函数会记忆先前操作的结果 记录在某个对象里 
    // 从而避免重复 运算 
    // js 的对象和数组 实现这种优化 非常方便 
    
    
    // 【※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※】
    var memoizer = function (memo,formula){
    
        var recur = function(n){
        
            var result = memo[n];
            if(typeof result !== 'number'){
                
                result = formula(recur,n);
                memo[n] = result;
            }
            return result;
        };
        return recur;
    }
    
    var fibonacci = memoizer([0,1],function(recur,n){
    
        return recur(n-1) + recur(n-2);
    });
    
    console.log(fibonacci(10));
    
    var factorial = memoizer([1,1],function(recur,n){
    
        return n * recur(n-1);
    });
    
    console.log(factorial(6));
    
    // 好么 这么好用 ~~~ 服了 !! 还是有点不太懂 ,我还得想想 ~~~
    

猜你喜欢

转载自blog.csdn.net/MENGCHIXIANZI/article/details/106123903