JavaScript--我發現,原來你是這樣的JS:函數表達式和閉包


一、介紹

本次博客主要介紹函數表達式的內容,主要是閉包。

二、函數表達式

定義函數的兩種方式:一個是函數聲明,另一個就是函數表達式。


//1.函數聲明寫法
function fn2(){
    console.log('函數聲明');  
}
//2.函數表達式寫法
var fn1 = function(){
    console.log('函數表達式');
}

區別:
1.函數聲明是用function后面有函數名,函數表達式是賦值形式給一個變量。
2.函數聲明可以提升函數,而函數表達式不會提升

函數提升就是函數會被自動提升到最前方,以至於再調用函數后再聲明函數也不會有錯:

//例子:
//先調用運行
sayName();
//再聲明函數
function sayName(){
    console.log('ry');
}

//運行結果
'ry'

函數表達式就不會被提升:

//先調用
sayBye();
//函數表達式
var sayBye = function(){
    console.log('bye bye');
}

//運行報錯

但是下面的寫法很危險:因為存在函數聲明的提升

//書上代碼
if(condition){
    function sayHi(){
        console.log('hi');
    }
}
else{
    function sayHi(){
        console.log('yo');
    }
}

解說一下: 這段代碼想表達在condition為true時聲明sayHi,不然就另一函數sayHi,但是運行結果往往出乎意料,在當前chrome或firefox可能能做到,但是在IE10以下的瀏覽器(我測試過)往往不會遵循你的意願,不管condition是true還是false都會輸出yo。

這時函數表達式能派上用場了:

//換成函數表達式,沒問題因為不會被提升,只有當執行時才賦值
var sayHi = null;
if(condition){
    sayHi = function (){
        console.log('hi');
    }
}
else{
    sayHi = function sayHi(){
        console.log('yo');
    }
}

三、閉包

閉包的定義:有權訪問另一個函數作用域中的變量的函數

有人覺得閉包很難理解,一開始我也是這樣的,我認為那是對一些概念還不夠了解。

定義中說明了什么是閉包,最常見的形式就是在函數中再聲明一個函數。

重點理解這里:

1.閉包能訪問外圍函數的變量是因為其作用域鏈中有外圍函數的活動對象(這個活動對象即使在外圍函數執行完還會存在,不會被銷毀,因為被閉包引用着)。

2.閉包是函數中的函數,也就是說其被調用時也創建執行上下文,對於執行上下文這部分可以看看這篇:深入理解js執行--創建執行上下文這篇博客。

理解了上面之后我們再來看閉包的例子:

function a(){
    //a函數的變量
    var val_a = "我是a函數里的變量";
    //聲明函數b,b能訪問函數a的變量
    function b(){
        console.log(val_a);
    }
    //a函數將b返回
    return b;
}

//全局變量fn,a執行返回了b給fn
var fn = a();
//調用fn,能夠在全局作用域訪問a函數里的變量
fn();  //我是a函數里的變量

這里fn能夠訪問到a的變量,因為b中引用着a的活動對象,所以即使a函數執行完了,a的活動對象還是不會被銷毀的。這也說明過度使用閉包會導致內存泄漏。

再來個常見的例子(給多個li添加點擊事件,返回對於li的下標):

<body>
    <ul id="list">
        <li>red</li>
        <li>green</li>
        <li>yellow</li>
        <li>black</li>
        <li>blue</li>
    </ul>
</body>
//獲得li標簽組
var li_group = document.getElementsByTagName('li');

//錯誤例子:每個li都會跳出5
function fn(){
    //為每一個li添加點擊事件
    var i = 0;
    //使用for來給每個li添加事件
    for(;i<li_group.length;i++){
        //添加點擊事件
        li_group[i].addEventListener('click',function(){
            // 輸出對應得下標
            console.log(i);
        });
    }
}
fn();


//正確的例子:
//在加一層的函數,作為閉包,用來保存每一次循環的i變量,就可以達到目的
function correctFn(){
    var i = 0;
    for(;i<li_group.length;i++){
        //在外面套一層函數,這層函數會保存每次循環的i變量,也就是閉包了。
        (function(num){
            li_group[num].addEventListener('click',function(){
                console.log(num);
            });               
        }(i));        
    }
}
correctFn();

看下面解釋之前我默認你已經知道活動對象是什么了,如果不懂可以看這篇:深入理解js執行--創建執行上下文

解釋一下:
1.錯誤的例子:
屢一下思路,每個li都有click執行的函數,每個函數點擊后才會執行是吧,那每個click的函數的外層函數是fn這個函數,那這5個click函數都會保存着fn的活動對象,那這個輸出的i變量在fn這函數里面,所以當fn執行完后,i的值是5了,因此當每個里觸發click的函數的時候輸出的也就是5了。
再簡單的說:每個li的click事件觸發的函數引用的i在同一個活動對象中,所以值都一樣。

2.正確執行的例子:
我們就在外層加了一層匿名函數並立即執行(雖然函數被執行了,如果有函數引用着它的活動對象,那其活動對象將不滅),這時每個li的click函數外層函數是每次循環產生的不同的匿名函數,這匿名也是有活動對象,每個li的click的函數保存着各自的匿名函數的活動對象,num這變量也根據每次循環產生不同的匿名函數傳入的i的不同而不同,所以能夠輸出對應不同的值。

上面說的可能有點啰嗦,請原諒我[捂臉.jpg],我是希望盡可能的表達清楚。如果你看懂了,那對閉包的理解也更深一層了哦。

小結:

1.本篇主要講的是閉包,閉包是有權訪問另一個函數作用域中的變量的函數,主要是函數中的函數,因為能引用外層函數的活動對象所以能夠訪問其外層的變量。
2.我本篇主要講的是原理,如果對一些東西不懂,可以看下面幾篇。

相關的幾篇:
深入理解js執行--單線程的JS
深入學習JS執行--創建執行上下文(變量對象,作用域鏈,this)
我發現,原來你是這樣的JS全部文章匯總(點擊此處)

本文出自博客園:http://www.cnblogs.com/Ry-yuan/
作者:Ry(淵源遠願)
歡迎訪問我的個人首頁:我的首頁
歡迎訪問我的github:https://github.com/Ry-yuan/demoFiles
歡迎轉載,轉載請標明出處,保留該字段。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM