作用域、执行环境、闭包(四)
本文也同步發表在我的公眾號“我的天空”
?
?
上一期我們已經介紹了閉包,由于閉包可以延長函數內部的變量的生存周期,因此我們可以將不需要暴露在全局的變量封裝成函數的內部變量,從而避免代碼污染。
?
譬如要實現一個簡單的累加器,為了保存每次累加的結果,因此聲明了一個全局變量total,代碼如下:
?
var total=0;
function add(t){
??? total =t;
??? alert(total);
}
total=2;
add(3);??????? //顯示5
add(5);??????? //顯示10
add(1);??????? //顯示11
?
但是在實際開發中,應盡量避免全局變量,因為全局變量可以在代碼的任何地方被調用,假設在其他地方,不小心更改了total的值的話,我們這個累加器就會出問題了。由于total值是只供函數add()使用的,因此希望total能被封閉在函數add()的內部,這樣,就無法從外部來改寫它了,我們使用閉包來實現,代碼如下:
?
function add(s){
??? var total=s;
??? return function(t){
?? ? ?? total =t;
??????? alert(total);
??? }
}
var a=add(2);
a(3);??????? //顯示5
a(5);??????? //顯示10
a(1);??????? //顯示11
?
通過閉包,將變量total封閉在函數add()中,外部無法訪問到,這樣就避免了被其他代碼隨意改寫的可能性。
?
由于閉包會將封閉在函數內部的局部變量賦予類似于全局變量的效果,因此在有些場景下需要特別注意,尤其是涉及到循環遍歷,來看以下代碼:
?
function createArray(){
??? var result=new Array();
??? for (var i=0;i<3;i ){
??????? result[i]=function(){
??????????? return i;
??????? }
??? }
}
?
這是一個創建數組數組的函數,從表面上看,每個函數應該都返回自己的索引值,因此創建的數組中,每個元素應該包含如下函數:
?
result[0]:function(){return 0}
result[1]:function(){return 1}
result[2]:function(){return 2}
?
但是實際上,數組中的每個元素只是包含:function(){return i},也就是說,當該函數執行完畢后,返回的是這樣一個數組:
?
{
??? function(){return i},
??? function(){return i},
??? function(){return i}
}
?
而變量i由于存在于一個返回函數中,形成了閉包,所以當createArray()執行完畢后,其執行環境不會被銷毀,變量i得以保留,并且其值為3(這點很重要)。
?
因此,當我們使用createArray()來創建數組時,得到的效果就不是我們的預期,彈出的都為“3”:
?
var a=createArray();
for(var z=0;z<a.length;z ){
??? alert(a[z]());??? //均顯示為3
}
?
實際上該代碼無非就是重復執行三遍以下代碼:
?
alert(function(){return 3}());
那么為了達到我們的預期,應該將createArray()函數做如下修改:
?
function createArray(){
??? var result=new Array();
??? for (var i=0;i<3;i ){
??????? result[i]=function(z){
??????????? return function(){
??????????????? return z;
??????????? };
??????? }(i)
??? }
}
?
分析以上代碼,我們將一個自執行函數返回給了數組元素,在賦值的時候,變量z就是在賦值的那個時刻的i值,那么返回的數組中的元素便包含我們預期的函數:
?
result[0]:function(){return 0}
result[1]:function(){return 1}
result[2]:function(){return 2}
?
再一次執行以下代碼,顯示就正常了:
?
var a=createArray();
for(var z=0;z<a.length;z ){
??? alert(a[z]());??? //依次顯示0、1、2
}
一定要注意的是,我們是把一個函數賦予了數組中的元素,而不是單個的值。因為在實際的應用中,返回函數的話,我們就可以在函數內做更多的事情。
?
看以下的實現,html有四個p標簽和4個div標簽,當單擊div標簽時相應的p標簽更改顏色,請注意這是一個面試中非常容易遇到的題目,代碼如下:
?
<body>
? <p>p1</p><p>p2</p><p>p3</p><p>p4</p>
? <div>div1</div><div>div2</div><div>div3</div><div>div4</div> ?
?</body>
? <script>
???? var d=document.getElementsByTagName("div");
?? ??? for(var i=0;i<d.length;i ){
?? ??? ? d[i].οnclick=function(num){
?? ??? ??? ? return function(){
?? ??? ??? ??? ?document.getElementsByTagName("p")[num].style.color="red";
?? ??? ??? ? };
?? ??? ? }(i);
?? ??? }
?</script>
?
如果直接寫成以下代碼的話,那么無論你單擊哪個div,程序總是會報錯,因為此時i的值為4,所以document.getElementsByTagName("p")[4]這個元素并不存在,導致引用錯誤。
?
?? for(var i=0;i<d.length;i ){
?? ???? d[i].οnclick=function(){
?? ??? ?? document.getElementsByTagName("p")[i].style.color="red";
? ?? ?};
?? }
?
最后我們要注意的是當需要返回函數內部的多個變量時,便不能采用返回匿名函數的方式了,可以采用以下的形式:
?
function setpepole(){
??? var name="李四";?? ?
??? var age=31;
??? return {
??????? getname:funcion(){
??????????? return name;?? ?
??????? },
??????? getage:function(){
??????????? return age;
??????? }
??? }
}
var a=setpepole();
alert(a.getname());???? //顯示“李四”
alert(a.getage());????? //顯示31
?
閉包系列就到此全部結束了!
?
更多專業前端知識,請上 【猿2048】www.mk2048.com
總結
以上是生活随笔為你收集整理的作用域、执行环境、闭包(四)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Day2 HTML基本标签元素
- 下一篇: AngularJS(九):路由