學習javascript一段時間了,經過師傅的指引,自己對閉包作出如下總結,如有某點不妥,請君指出,不勝感激!
要理解閉包,首先必須理解Javascript特殊的變量作用域。
變量的作用域無非就是兩種:全局變量和局部變量。
Javascript語言的特殊之處,就在於函數內部可以直接讀取全局變量,而在函數外部無法讀取函數內的局部變量。
注意點,函數內部聲明變量的時候,一定要使用var命令。否則變為全局變量。
簡而言之,閉包就是一個受到保護的變量空間。
閉包案例
functon(){ var num = Math.random; return num; } var res = foo(); 在這不能直接訪問函數內部變量num,只可以間接訪問 var r1 = foo(); var r2 = foo(); alert(r1 + '\n'+ r2); 如果使用return返回函數內的數據,理論上不是訪問一個數據, 因為在函數運行會分配內存空間,函數調用的時候,數據也會再次創建
若要解決以上問題,獲得相同的數據如何操作
function (){ var num = Math.random(); return function (){ return num; } } var fn = foo();//函數調用一次num就是唯一的 var r1 = fn(); var r2 = fn(); alert(r1 + '\n' +r2) //fn是在一個在函數內定義的函數,那麼在執行時候可以訪問到上一級作用域中的num , 因此在最外面,就可以間接訪問唯一的num。此處就是用到了閉包,此次起到保護數據的我作用。
函數的本質:Function的實例,也是對象。
執行一個函數,讓函數返回對象
function Foo(){ var o = {num: 123}; return o; } var obj = Foo(); alert(obj.num);//123
在調用函數時,函數內部創建一個對象
o中存儲著對象的引用,return是將o中的數據拷貝一份再返回
返回的結果被obj接收,此時obj存儲的是對象的引用
執行一個函數,讓函數返回一個函數
function Foo(){ var o = new Function('alert(123)'); return o; } var fn = Foo(); 相當於var fn = new Functon('alert(123)'); alert(fn);//123
執行一個函數,讓函數返回一個數組
function func(){ var m = Math.random(); var n = Math.random(); return [ function () { return m;} function () { return n;} ] } // var fns = func(); // var m1 = fns[0](); // var n1 = fns[1](); // alert(m1 +','+ n1) ; // var m2 = fns[0](); // var n2 = fns[1](); // alert(m2 +','+ n2) ;
在以上兩組數據中輸出的結果是相同的,
數組的優點體現出數據的有序性 多個數據可以進行排序
但是在復雜數據中,數據的序號就不再有優勢
可以用對象對以上函數改進
function func(){ var m = Math.random(); var n = Math.random(); return { get_M: function (){ return m;}, get_N: function (){ return n;} }; } // var obj = func(); // var m1 =obj.get_M(); // var n1 = obj.get_N(); // alert(m1 +','+ n1) ;
閉包案例之調用一函數提供兩個方法,對num進行賦值和讀取
第一種寫法
function Foo(){ var obj = new Object(); //這裡也可以寫成 var obj = {};?? var num; obj.get_num = function(){ return num; }; obj.set_num = function(v){ num = v; }; return obj; } var o = Foo(); console.log(o.get_num());//undefined console.log(o.set_num(11));//undefined console.log(o.get_num(11));//11
第二種寫法
function Foo() { var num; return { get_num: function () { return num; }, set_num: function ( v ) { num = v; } }; } var o = Foo(); console.log( o.get_num() ); //undefined console.log(o.set_num( 11 ));//undefined console.log( o.get_num() );//11
這有一個對以上函數的變式
function Foo() { var num; return { _num: num,//賦值操作 set_num: function ( v ) { num = v; } }; } //相當於下面函數 function Foo() { var num; var obj = {}; obj._num = num;//上面聲明的num和此時的_num變量是不同的 obj.set_num = function ( v ) { num = v; }; return obj; } var o = Foo(); console.log( o._num ); //undefined console.log(o.set_num(11)); //undefined console.log(o._num); //undefined 取不出數據
閉包的應用:實現私有數據和緩存數據
閉包案例之斐波那契數列
沒使用閉包的函數式
var count = 0; var fib = function (n){ count++; if(n< 0)throw new Error('不允許出現負數'); if(n === 0||n === 1)return 1; // return fib(n-1)+fib(n-2); return arguments.callee(n-1) + arguments.callee(n-2); } console.log(fib(16)); console.log(count); //分別計算第1、2、4、8、16、32項對應的次數為1、3、9、67、3193、7049155
從以上計算的次數可以看出性能的損耗很嚴重,那麼閉包可以在此解決的問題是已經運算過得數據緩存下來
var count = 0; var fib = (function(){ var arr = []; return function (n){ count++; if(n < 0)throw new Error('不允許出現負數'); var res = arr[n];//緩存數據 判斷有無數據 if(res !== undefined){ return res; } else { if(n === 0||n ===1) { res = 1; } else{ res = fib(n - 1)+fib(n - 2); } } arr[n] = res; return res; } })(); console.log(fib(100)); console.log(count);//199
第二種寫法
var count = 0; var fib = (function(){ var arr = []; return function(n){ count++; return feibo(arr,n); } })(); function feibo(arr,n){ if(n < 0)throw new Error("不允許出現負數"); var res = arr[n]; if(res != undefined){ return res; }else{ if(n === 0 ||n === 1){ res = 1; }else{ res = fib(n - 1) + fib(n - 2); } } arr[n] = res; return res; } console.log(fib(100)); console.log(count);
從上式可以看出閉包帶來的好處;
拓展:談到數據緩存也可以不用閉包,下面函數則與閉包無關
var fib = function ( n ) { var res = fib[ n ]; // 先到函數名中取 if ( res !== undefined ) { return res; } else { // 如果是 1 或 0 則將 1 返回給 res // 否則遞歸結果交給 res; if ( n === 0 || n === 1 ) { res = 1; } else { res = arguments.callee( n - 1 ) + arguments.callee( n - 2 ); } fib[ n ] = res; // 將計算的結果放到數組中, 那麼下一次再計算的 // 時候可以直接拿來用, 就不用重新計算 fib.len++;//每次賦值完後 return res; } }; fib.len = 0;//給函數添加一個屬性 console.log(fib(100)); console.log(fib.len)//101