【書 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));
    
    // 好麼 這麼好用 ~~~ 服了 !! 還是有點不太懂 ,我還得想想 ~~~
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章