閉包是什么
對(duì)于這個(gè)問題,我只能給你淫一句詩:
" 不識(shí)廬山真面目,只緣身在此山中 "
閉包的使用在我們的平時(shí)代碼的書寫和使用中太常見了,我舉幾個(gè)栗子給你瞧瞧!
function foo(){ var name = "LiAo"; function bar(){ console.log(name); } return bar; } var baz = foo(); baz(); // "LiAo"
function wait(msg){ setTimeout(function timer(){ console.log(msg); },1000); wait("Hello ,LiAo");
var a = 2 ; (function IIFE(){ console.log(a); })();
這些栗子都使用了閉包,其實(shí)閉包的本質(zhì)是內(nèi)部作用域可以訪問外部作用域的原則续挟,不管內(nèi)部函數(shù)在哪里被調(diào)用钮孵,它都能夠訪問到外部函數(shù)的作用域
關(guān)于閉包的經(jīng)典問題
①
for(var i=1;i<=5;i++){ setTimeout(function timer(){ console.log(i); },i*1000); }
這種問題你應(yīng)該看到過很多遍了吧,毫無疑問,運(yùn)行時(shí)會(huì)以每秒一次的頻率輸出五次6(等setTimeout里面的函數(shù)開始運(yùn)行時(shí),for循環(huán)早已完成,i早已置為6,而此時(shí)每個(gè)回調(diào)函數(shù)都在引用完成時(shí)候的變量i,自然都是輸出6),
為了達(dá)到預(yù)期的效果禽篱,有很多種辦法
for(var i=1;i<=5;i++){ (function(){ var j = i; setTimeout(function timer(){ console.log(j); },j*1000); })() }
通過一個(gè)IIFE來創(chuàng)建作用域,此時(shí)每個(gè)循環(huán)都有一個(gè)塊級(jí)作用域馍惹,由最初的共享i變成分別訪問自己塊級(jí)作用域的j躺率,而這個(gè)j又保存著執(zhí)行循環(huán)時(shí)候i的值,從而得到了正確的結(jié)果
for(var i=1;i<=5;i++){ (function(j){ var j = i; setTimeout(function timer(){ console.log(j); },j*1000); })(i) }
其實(shí)這種方法跟上面那種是一樣的道理万矾,只不過這里采用隱式賦值悼吱,還記得嗎,js函數(shù)的參數(shù)是按值傳遞的良狈,因?yàn)閷傳給立即執(zhí)行函數(shù)后添,相當(dāng)于執(zhí)行了var j =i;從而保存了到時(shí)的i值们颜,得到了正確的結(jié)果吕朵。
for(var i=1;i<=5;i++){ let j=i; setTimeout(function timer(){ console.log(j); },j*1000); }
這種方法使用了ES6的let標(biāo)識(shí)符,let標(biāo)識(shí)符讓所在代碼塊能獨(dú)立成為一個(gè)作用域窥突,因而保存了正確的值。
上面的方法也可以簡化為下面的形式
for(let i=1;i<=5;i++){ setTimeout(function timer(){ console.log(i); },i*1000); }
②
var name = "The Window"; var object = { name:"My Object", getNameFunc:function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); //"The Window"
這個(gè)問題是匿名函數(shù)并沒有渠道其包含作用域的this對(duì)象
因?yàn)槊總€(gè)函數(shù)在被調(diào)用時(shí)候都會(huì)自動(dòng)取得兩個(gè)特殊變量:this和arguments硫嘶,這兩個(gè)變量在函數(shù)被調(diào)用時(shí)動(dòng)態(tài)綁定阻问。內(nèi)部函數(shù)在搜索這兩個(gè)變量時(shí),只會(huì)搜索到其活動(dòng)對(duì)象為止沦疾,因此永遠(yuǎn)不能直接訪問到外部函數(shù)中的這兩個(gè)變量称近。
解決方案
var object = { name:"My Object", getNameFunc:function(){ var that = this; return function(){ return that.name; } } }
③
內(nèi)存泄漏問題
function assignHandler(){ var element = document.getElementById("someElement"); element.onclick = function(){ alert(element.id); } }
在這里我們只需要獲得元素的id值即可,而elment.id直接引用了元素本身哮塞,導(dǎo)致這元素?zé)o法被銷毀刨秆,堆積多了,自然會(huì)導(dǎo)致性能下降
解決方案
function assignHandler(){ var element = document.getElementById("someElement"); var id =element.id; element.onclick = function(){ alert(id); } element = null; }
這里我們只需要保存元素的值就行忆畅,不用引用該元素衡未,另外,即使閉包不直接引用element家凯,包含函數(shù)的活動(dòng)對(duì)象也會(huì)仍然保存一個(gè)引用缓醋,所以有必要把element變量設(shè)置為null。
閉包的使用
模仿塊級(jí)作用域
(function(){ //這里是塊級(jí)作用域绊诲; })();
私有變量
function MyOject(){ var privateVariable = 10; function privateFunction(){ return false; } this.publicMethod = function(){ privateVariable++; return privateFunction(); } }
優(yōu)點(diǎn): 每個(gè)實(shí)例都有自己的私有變量和方法
缺點(diǎn):針對(duì)每個(gè)實(shí)例都會(huì)無必要的創(chuàng)建同一組新方法送粱。
靜態(tài)私有變量
function(){ var money = 10; Couple = function(name,age){ this.name = name; this.age = age; }; Couple.prototype.getMoney = function(){ console.log("You have "+money+" money at all") return money; } Couple.prototype.storage = function(num){ money += num; console.log("storage success and you only have "+ money+" at all") } Couple.prototype.draw = function(num){ if(money>=num){ money -= num; console.log("draw success and you only have "+ money+" at all") }else{ console.log("Error! You haven't enough money and the money only remains "+money) } } })(); var LiAo = new Couple("LiAo",22); var Lan = new Couple("Lan",21); LiAo.getMoney(); Lan.getMoney(); LiAo.storage(1000); Lan.draw(500); LiAo.getMoney(); Lan.getMoney(); LiAo.draw(300); Lan.draw(800); You have 10 money at all You have 10 money at all storage success and you only have 1010 at all draw success and you only have 510 at all You have 510 money at all You have 510 money at all draw success and you only have 210 at all
這里是我想到的一種應(yīng)用場(chǎng)景:夫妻共同使用一個(gè)銀行賬戶,固然存款是私有的掂之,不能被直接訪問的變量抗俄,但是夫妻都有方法進(jìn)行存和取錢脆丁。兩者的存取都會(huì)影響到他們共同的存款即私有變量,靜態(tài)私有變量例子的每個(gè)實(shí)例都是共享同一個(gè)私有變量的动雹,所以適合用于多個(gè)實(shí)例共同使用一個(gè)變量的情況偎快。而前面的私有變量例子是每個(gè)實(shí)例都有自己的私有變量,互不影響洽胶。
模塊模式
模塊模式是為單例創(chuàng)建私有變量和特權(quán)方法,而在javascript中是以對(duì)象字面量的方式來創(chuàng)建單例對(duì)象的
var singleton = function(){ var privateVariable = 10; function privateFunction(){ return false; } return { publicProperty:true, publicMethod:function(){ privateVariable++; return privateFunction(); } }; }();
這種模式在需要對(duì)單例進(jìn)行某些初始化,同時(shí)又需要維護(hù)其私有變量時(shí)是非常有用的.
增強(qiáng)的模塊模式
增強(qiáng)的模塊模式適合那些單例必須是某種類型的實(shí)例,同時(shí)還必須添加某些屬性和(或)方法對(duì)其加以增強(qiáng)的情況
如:
var singleton = function(){ var privateVariable = 10; function privateFunction(){ return false; } var object = new CustomType(); object.publicMethod = function(){ privateVariable++; return privateFunction(); } return object; }();