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

1、介紹

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

2、函數表達式

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

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

區別:
1.函數聲明是用function後面有函數名,函數表達式是賦值形式給一個變量。
2.函數聲明能夠提高函數,而函數表達式不會提高github

函數提高就是函數會被自動提高到最前方,以致於再調用函數後再聲明函數也不會有錯:chrome

//例子:
//先調用運行
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');
    }
}

3、閉包

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

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

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

重點理解這裏:

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 歡迎轉載,轉載請標明出處,保留該字段。

相關文章
相關標籤/搜索