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