1. 程式人生 > >js 高階函式 閉包

js 高階函式 閉包

摘自  https://www.cnblogs.com/bobodeboke/p/5594647.html

建議結合另外一篇關於閉包的文章一起閱讀:http://www.cnblogs.com/bobodeboke/p/6127650.html

一、閉包

閉包某種程度上就是函式的內部函式,可以引用外部函式的區域性變數。當外部函式退出後,如果內部函式依舊能被訪問到,那麼內部函式所引用的外部函式的區域性變數就也沒有消失,該區域性變數的生存週期就被延續。

一個經典的例子如下:

複製程式碼
<script>
    //this丟失現象
    document.addEventListener('DOMContentLoaded',function(){
    var divs=document.getElementsByTagName('div');
        console.log(divs);
        for (var i = 0; i < divs.length; i++) {
            divs[i].onclick=function(){
                alert(i);
            }
        };    
    },false);
    
        
    </script>
</head>
<body>
    <div id="div1">testDiv</div>
    <div id="div2">div2</div>
</body>
複製程式碼

上面的程式碼中,因為div節點的Onclick事件是非同步觸發的,當事件被觸發的時候,for迴圈早已結束,此時變數i的值已經是迴圈結束時候的2;如果想要達到想要的效果,需要採用閉包的形式,具體如下:

複製程式碼
    var divs=document.getElementsByTagName('div');
        //console.log(divs);
        for (var i = 0; i < divs.length; i++) {
            (function(i){
                divs[i].onclick=function(){
                    alert(i);
                }
            })(i);
        };    
    },false);
複製程式碼

或者這種寫法經過測試也是可行的:

 

複製程式碼
    var divs = document.getElementsByTagName('div');
    for (var i = 0, len = divs.length; i < len; i++) {
      divs[i].onclick = (function(i) {
        return function() {
          alert(i);
        };
      })(i);
    }
複製程式碼

 

 

 

注意不要寫成下面這樣,這和第一種並沒有本質區別:

複製程式碼
    var divs=document.getElementsByTagName('div');
        //console.log(divs);
        for (var i = 0; i < divs.length; i++) {
            divs[i].onclick=function(){
                (function(i){
                    alert(i);
                })(i); //此時蹦出來的都是最後一個i值
            }
        };    
    },false);
複製程式碼

也不要寫成這種:

 

複製程式碼
    var divs = document.getElementsByTagName('div');
    for (var i = 0, len = divs.length; i < len; i++) {
      divs[i].onclick = (function(i) {
        return function(i) {
          alert(i);// 此時彈出來的是[object MouseEvent]
        };
      })(i);
    }
複製程式碼

 

 

 

二、高階函式

高階函式是至滿足下列條件之一的函式:

  1)函式可以作為引數被傳遞(如回撥函式等);

  2)函式可以作為返回值輸出;

高階函式還應用於以下場景:

  1)高階函式實現AOP

  (AOP面向切面程式設計,其主要作用是把一些跟核心業務邏輯模組無關的功能抽離出來,這些無關模組通常包括日誌統計,安全控制,異常處理等,然後再將這些支撐模組“動態織入”到另一個函式中去),在java中通常是適用反射和動態代理模式來實現AOP,而js中可以很方便的利用高階函式實現AOP程式設計。 下例實際上就是針對函式的裝飾者模式;

 

複製程式碼
        Function.prototype.before=function(beforeFn){
            //假設呼叫的時候一般是fna.before(fnb);則這裡的this是fna
            var self=this;
            //console.log(this);
            //這裡的this是裝飾之後的函式呼叫的上下文,例子上f(3)呼叫時,沒有顯式的上下文,因此此時是window
            //arguments即真正呼叫的時候傳入的引數,此時beforeFn與self傳入的是同一個引數,在例子中就是3
            return function(){
                //console.log(this);
                //console.log(arguments);
                beforeFn.apply(this,arguments);
                
                return self.apply(this,arguments);
            }
        };
        Function.prototype.after=function(afterFn){
            var self=this;
            return function(){
                var ret=self.apply(this,arguments);
                afterFn.apply(this,arguments);
                return ret;
            };
        };
        function fna(a){
            console.log(1+a);
        }
        function fnb(a){
            console.log(2+a);
        }
        var f=fna.before(fnb);
        f(3);
複製程式碼

 

 

 

  2)函式柯里化(currying)

函式柯里化currying又稱為部分求值,一個currying的函式會先接受一些引數,接收了這些引數以後,該函式並不會立即求值,而是繼續返回另外一個函式,剛才傳入的引數在函式形成的閉包中被儲存,待函式真正需要求值的時候,之前傳入的所有引數都會被一次性的用於求值。

下面是一個通用的函式currying的實現:

 

複製程式碼
        var currying=function(fn){
            var args=[];
            return function(){
                if(arguments.length>=1){
                    [].push.apply(args,arguments);
                    //其實這裡有沒有返回值不是必須的
                    //return arguments.callee;
                    //return fn;
                }else{
                    return fn.apply(this,args);
                }
            };
        };
        function cost(){
            var money=0;
            for(var i=0;i<arguments.length;i++){
                money+=arguments[i];
            }
            console.log(money);
            return money;
        }
        var cost=currying(cost);
        cost(200);//未真正求值
        cost();//進行真正求值
複製程式碼

 

    3)函式節流

  針對一些被頻繁呼叫的函式,如onresize,mousemove等,它們共同的特徵是函式被觸發的頻率太高,事實上可能並不需要以這麼高的頻率呼叫,下面的程式碼可以對此類函式指定觸發的間隔。

複製程式碼
        var throttle=function(fn,interval){
            var timer,
            firstTime=true;
            return function(){
                if(firstTime){
                //第一次的時候,不延遲執行
                    fn.apply(this,arguments);
                    return firstTime=false;
                }
                
                if(timer){
                    return false;
                }
                //延時一段時間之後執行
                timer=setTimeout(function(){
                    //清除定時器
                    clearTimeout(timer);
                    timer=null;
                    fn.apply(this,arguments);
                },interval||1000);
                
            };
        };
        var i=1;
         
        window.onresize=throttle(function(){
            console.log(i++);
        }); 
複製程式碼

  4)分時函式

頁面短時間內進行大量DOM操作會造成頁面卡主的情況,比如需要迴圈在頁面上新增1000個DOM節點,一種解決方案是下面的timeChunk函式,讓原本1s鍾建立1000個節點的操作,改為每200ms建立8個節點。

timeChunk接收三個引數,第一個引數是建立節點需要的資料,第二個引數是封裝建立邏輯的函式,第三個引數表示每一批建立的節點數量。

 

複製程式碼
        var timeChunk=function(ary,fn,count){
            var timer;
            return function(){
                var operation=function(){
                    for(var i=0;i<Math.min(count||1,ary.length);i++){
                        var curData=ary.shift();
                        fn(curData);
                    }
                };
                timer=setInterval(function(){
                    if(ary.length<=0){
                        clearInterval(timer);
                        timer=null;
                        return;
                    }
                    operation();
                },200);
            }
        };
複製程式碼

 另外一篇關於高階函式的文章

https://www.cnblogs.com/goloving/p/8361705.html