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
js 高階函數 閉包