學習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