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

js 高階函數 閉包

see bar 數據 方便 生存 elements 一個 rgs 現象

摘自 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

js 高階函數 閉包