上個月,淡丶無慾 讓我寫一期關於 閉包 的隨筆,其實慚愧,我對閉包也是略知一二 ,不能給出一個很好的解釋,擔憂本身講不出個因此然來。 因此帶着學習的目的來寫一寫,若有錯誤,忘不吝賜教 。javascript
初識閉包時,,我一直在想,爲何只有JS有閉包,c#,c++ 爲何沒有 ??html
看下面一個例子,計算 斐波那契 數。
爲了可以重用數據,一個通用作法就是將計算過的數據緩存起來,但緩存的數據對外是不可見的 。
看下面的 c# 代碼 :java
public static class Fibonacci{ public static Fibonacci(){ cache[0] = 1; cache[1] = 1; } private static IList<int> cache = new List<int>(1000,-1); public static int Calc(n){ if(cache[n] != -1){ return cache[n]; }else{ return cache[n] = Calc(n-1) + Calc(n-2); } } }
快兩年沒寫c#了, 很撇腳,囧 ,可是在這類靜態語言,這種方法很合適 。c++
看JS 怎麼寫面試
var cache = [1, 1]; var calc = function(){ return cache[n] != undefined ? cache[n]: cache[n] = calc(n-1) + calc (n-2); }
可是在JS中杜絕使用全局變量,因此下面改寫編程
var calc = function(){ return calc.cache[n] != undefined ? calc.cache[n]: calc.cache[n] = calc.cache(n-1) + calc.cache (n-2); } calc.cache = [1,1];
這裏將全局變量做爲 calc 的一個屬性存儲,可是對外可見,沒法隱藏 。c#
就到了閉包大顯身手的時候,因爲這裏 cache 被外部調用,因此能夠不被銷燬。設計模式
var Fibonacci = (function() { var cache = [1, 1]; return { calc: function(n) { return cache[n] != undefined ? cache[n] : cache[n] = this.calc(n - 1) + this.calc(n - 2); } } })(); Fibonacci.calc(5); // 8
總結:
在 c# ,c++ 等高級語言中,存在私有變量,因此無需閉包 。可是在JS中,私有變量是一件很麻煩的事情 。這時候,將局部變量放置在一個函數做用域中,能夠在內部使用,而外面沒法訪問。這就造成了閉包 。瀏覽器
對於全局變量來講,全局變量的生存週期固然是永久的,除非咱們主動銷燬這個全局變量。
而對於在函數內用var關鍵字聲明的局部變量來講,當退出函數時,這些局部變量即失去了它們的價值,它們都會隨着函數調用的結束而被銷燬:
如今來看看下面這段代碼:緩存
var func = function(){ var a = 1; return function(){ a++; alert ( a ); } }; var f = func(); f(); // 輸出:2 f(); // 輸出:3 f(); // 輸出:4 f(); // 輸出:5
跟咱們以前的推論相反,當退出函數後,局部變量a並無消失,而是彷佛一直在某個地方存活着。這是由於當執行 var f=func();時,f返回了一個匿名函數的引用,它能夠訪問到 func()被調用時產生的環境,而局部變量 a 一直處在這個環境裏。既然局部變量所在的環境還能被外界訪問,這個局部變量就有了不被銷燬的理由。在這裏產生了一個閉包結構,局部變量的生命看起來被延續了。
這個特性有時候會很麻煩,但有時候頗有用,用來延續局部變量的壽命 。
img 對象常常用於進行數據上報,以下所示:
var report = function( src ){ var img = new Image(); img.src = src; }; report( 'http://xxx.com/getUserInfo' );
可是經過查詢後臺的記錄咱們得知,由於一些低版本瀏覽器的實現存在bug,在這些瀏覽器
下使用report函數進行數據上報會丟失30%左右的數據,也就是說,report函數並非每一次都成功發起了 HTTP請求。 丟失數據的緣由是 img 是 report 函數中的局部變量, 當 report 函數的調用結束後,img 局部變量隨即被銷燬,而此時或許還沒來得及發出 HTTP請求,因此這次請求就會丟失掉。
如今咱們把 img 變量用閉包封閉起來,便能解決請求丟失的問題:
var report = (function(){ var imgs = []; return function( src ){ var img = new Image(); imgs.push( img ); img.src = src; } })();
閉包在JS中很是普遍,經常與高階函數做伴 。
高階函數是指至少下面條件之一的函數
函數做爲參數傳遞做爲回調函數,應用場景很是普遍,此處再也不舉例 。
函數做爲返回值,這裏給出兩個例子 。
判斷數據類型
'use strict'; var Type = {}; for (var i = 0, type; type = ['String', 'Number', 'Boolean', 'Object'][i++];) { (function(type) { Type["is" + type] = function(o) { return Object.prototype.toString.call(o) === '[object ' + type + ']'; } })(type); } console.log(Type.isString("hh"));
實現單例模式
var getSingle = function(func){ var ret = null; return function(){ return ret || ret = func.apply(this, Array.prototype.slice.call(arguments)); } } var getScript = getSingle(function(){ return document.createElement( 'script' ); }); var script1 = getScript(); var script2 = getScript(); alert ( script1 === script2 ); // 輸出:true
AOP(面向切面編程)的主要做用是把一些跟核心業務邏輯模塊無關的功能抽離出來,這些
跟業務邏輯無關的功能一般包括日誌統計、安全控制、異常處理等。把這些功能抽離出來以後 。再經過「動態織入」的方式摻入業務邏輯模塊中。這樣作的好處首先是能夠保持業務邏輯模塊的純淨和高內聚性,其次是能夠很方便地複用日誌統計等功能模塊。
在傳統經過on = 爲事件註冊處理程序中,賦值一個新的處理程序會覆蓋掉原來的處理程序,咱們能夠這麼作
var addEvent = function(target,event ,func){ var old; target[event] = functoin(e){ if(old = target[event]){ old(); } func(); } }
一般,在 JavaScript中實現 AOP,都是指把一個函數「動態植入」到另一個函數之中,具
體的實現技術有不少,本節咱們經過擴展 Function.prototype 來作到這一點
Function.prototype.before = function(beforeFn) { var self = this; return function() { beforeFn.apply(this, Array.prototype.slice.call(arguments)); return self.apply(this, Array.prototype.slice.call(arguments)); } }; Function.prototype.after = function(afterFn) { var self = this; return function() { var ret; ret = self.apply(this, Array.prototype.slice.call(arguments)); afterFn.apply(this, Array.prototype.slice.call(arguments)); return ret; } } var func = function() { console.log(2); } func = func.before(function() { console.log(1); }).after(function() { console.log(3); }) func();
輸出 1 2 3 。
return 後的函數中的this 取決於真實環境的this ,由於返回的是一個獨立的函數 。
currying又稱部分求值。一個currying的函數首先會接受一些參數,接受了這些參數以後,該函數並不會當即求值,而是繼續返回另一個函數,剛纔傳入的參數在函數造成的閉包中被保存起來。待到函數被真正須要求值的時候,以前傳入的全部參數都會被一次性用於求值。
看下面一個例子
func(1); 1 func(1)(2); 2 func(1)(2)(3); 6 ...
這個例子就是函數柯里化的典型應用,重點考察閉包和高階函數,也是一道比較常見的面試題
看下面的解法 。
'use strict'; var curry = (function() { var data = [1]; var func = function(n) { data.push(n); return func; } func.valueOf = function() { var ret = data.reduce(function(a, b) { return a * b; }) data = [1]; return ret; } return func; })(); console.log(curry(1)); console.log(curry(1)(2)); console.log(curry(1)(2)(3));
在上面的解法中,咱們將函數柯里化和數據計算放在一塊兒,違背了單一職責原則 。如今,咱們能夠專門定義一個函數,用於對參數進行柯里化。
'use strict'; var curry = function(fn) { var args = []; var ret = function(n) { args.push(n); return ret; } ret.valueOf = function() { var ret = args.reduce(fn); args = []; return ret; } return ret; } var func = curry(function(a, b) { return a * b; }) console.log(func(1)); console.log(func(1)(2)); console.log(func(1)(2)(3));
若是有錯誤,但願不吝賜教 ~
注: 這篇隨筆的一些例子代碼來自於 《JavaScript 設計模式與實踐 》 ,書中很是詳細並深刻講解了JavaScript 高級和 17 種設計模式,對於JavaScript提升很是有幫助 。安利 ~ 。 想要電子版能夠給我發郵件: mymeat@126.com
轉載請說明原文出處:http://www.cnblogs.com/likeFlyingFish/p/6421615.html